136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik/*
2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 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
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget;
1836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
19c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapardimport static org.hamcrest.CoreMatchers.is;
20c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapardimport static org.hamcrest.MatcherAssert.assertThat;
21c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapardimport static org.hamcrest.core.IsEqual.equalTo;
22945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craikimport static org.junit.Assert.assertEquals;
230fab45107f043a4856aad2e1fd785b396898616eChris Craikimport static org.junit.Assert.assertFalse;
241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.junit.Assert.assertNotEquals;
25dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craikimport static org.junit.Assert.assertNotNull;
260fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Guimport static org.junit.Assert.assertSame;
27ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue;
28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any;
29ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt;
301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.mockito.Matchers.argThat;
31ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock;
32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never;
33ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times;
34ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify;
35ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when;
36ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas
3736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context;
38e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build;
390ceae57ee14da826c31a66c67e70f7f17108d5caChris Craikimport android.os.Parcelable;
400951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.os.SystemClock;
4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry;
42ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress;
43754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.SmallTest;
4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4;
450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.view.MotionEvent;
4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View;
4736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup;
480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.widget.FrameLayout;
4936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
50c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull;
51c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
52356880d3de117b067522ad452f4e3eed85ce444cChris Craikimport org.junit.After;
5336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before;
5436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test;
5536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith;
56963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikasimport org.mockito.ArgumentMatcher;
5736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock;
5836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer;
5936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport java.util.ArrayList;
6136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List;
62a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit;
6336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
6436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest
65e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
6636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class)
6736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest {
68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    TimeMockingRecyclerView mRecyclerView;
6936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    RecyclerView.Recycler mRecycler;
7036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
71a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    private class TimeMockingRecyclerView extends RecyclerView {
72a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        private long mMockNanoTime = 0;
73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
74a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        TimeMockingRecyclerView(Context context) {
75a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            super(context);
76a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
77a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
78a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        public void registerTimePassingMs(long ms) {
79a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms);
80a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
81a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
82a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        @Override
83a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        long getNanoTime() {
84a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            return mMockNanoTime;
85a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
86bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik
87bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        @Override
88bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        public int getWindowVisibility() {
89bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            // Pretend to be visible to avoid being filtered out
90bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            return View.VISIBLE;
91bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        }
92356880d3de117b067522ad452f4e3eed85ce444cChris Craik    }
93a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
9436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Before
95356880d3de117b067522ad452f4e3eed85ce444cChris Craik    public void setup() throws Exception {
96a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView = new TimeMockingRecyclerView(getContext());
97356880d3de117b067522ad452f4e3eed85ce444cChris Craik        mRecyclerView.onAttachedToWindow();
9836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler = mRecyclerView.mRecycler;
9936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
10036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
101356880d3de117b067522ad452f4e3eed85ce444cChris Craik    @After
102356880d3de117b067522ad452f4e3eed85ce444cChris Craik    public void teardown() throws Exception {
103356880d3de117b067522ad452f4e3eed85ce444cChris Craik        if (mRecyclerView.isAttachedToWindow()) {
104356880d3de117b067522ad452f4e3eed85ce444cChris Craik            mRecyclerView.onDetachedFromWindow();
105356880d3de117b067522ad452f4e3eed85ce444cChris Craik        }
106356880d3de117b067522ad452f4e3eed85ce444cChris Craik        GapWorker gapWorker = GapWorker.sGapWorker.get();
107356880d3de117b067522ad452f4e3eed85ce444cChris Craik        if (gapWorker != null) {
108356880d3de117b067522ad452f4e3eed85ce444cChris Craik            assertTrue(gapWorker.mRecyclerViews.isEmpty());
109356880d3de117b067522ad452f4e3eed85ce444cChris Craik        }
110356880d3de117b067522ad452f4e3eed85ce444cChris Craik    }
111356880d3de117b067522ad452f4e3eed85ce444cChris Craik
11236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    private Context getContext() {
11336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        return InstrumentationRegistry.getContext();
11436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
11536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
116dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    private void layout(int width, int height) {
117dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.measure(
118dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
119dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
120dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.layout(0, 0, width, height);
121dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    }
122dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchReusesCacheItems() {
12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() {
12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
12836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
12936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        ViewGroup.LayoutParams.WRAP_CONTENT);
13036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
13136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
13236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
1331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
1343104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik                    LayoutPrefetchRegistry prefetchManager) {
135945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(0, 0);
136945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(1, 0);
137945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(2, 0);
13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        };
14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(prefetchingLayoutManager);
14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(new View(getContext())) {};
15336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
15436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
15536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(10);
15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
158dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(320, 320);
15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt());
16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onBindViewHolder(
16236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
16336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
16436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Prefetch multiple times...
16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        for (int i = 0; i < 4; i++) {
16747d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik            mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            // ...but should only see the same three items fetched/bound once each
17036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt());
17136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onBindViewHolder(
17236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            assertTrue(mRecycler.mCachedViews.size() == 3);
1751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 0, 1, 2);
17636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
17736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
17836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedWithInserts() {
181dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
18436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
18536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
18636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
18736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
18836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
189dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        View view = new View(getContext());
190dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        view.setMinimumWidth(100);
191dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        view.setMinimumHeight(100);
192dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        return new RecyclerView.ViewHolder(view) {};
19336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
19436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
19536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
19636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
19736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
198dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 100);
19936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
200945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(2, mRecyclerView.mRecycler.mViewCacheMax);
201dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
20247d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
203945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.mRecycler.mViewCacheMax);
204945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
2051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 3, 4, 5);
20636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // further views recycled, as though from scrolling, shouldn't evict prefetched views:
20836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(10));
209dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10);
21036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(20));
212dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10, 20);
21336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(30));
215dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 20, 30);
21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(40));
218dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 30, 40);
21936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // After clearing the cache, the prefetch priorities should be cleared as well:
22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.mRecycler.recycleAndClearCachedViews();
222dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        for (int i : new int[] {3, 4, 5, 50, 60, 70, 80, 90}) {
22336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            mRecycler.recycleView(mRecycler.getViewForPosition(i));
22436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // cache only contains most recent positions, no priority for previous prefetches:
227356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 50, 60, 70, 80, 90);
22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedOnScroll() {
23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
23436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // 100x100 pixel views
23536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
23636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        View view = new View(getContext());
24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumWidth(100);
24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumHeight(100);
24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(view) {};
24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
24836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
24936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
25036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // NOTE: requested cache size must be smaller than span count so two rows cannot fit
25136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setItemViewCacheSize(2);
25236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
253dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 150);
25436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 75);
25536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
25636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
25736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // rows 0, 1, and 2 are all attached and visible. Prefetch row 3:
258945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
25947d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
26036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is cached:
262356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11);
26336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
26436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Scroll so 1 falls off (though 3 is still not on screen)
26636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 50);
26736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is still cached, with a couple other recycled views:
269356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11);
27036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 5);
27136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
272a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
273a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    @Test
274b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    public void prefetchIsComputingLayout() {
275b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
276b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
277b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        // 100x100 pixel views
278b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
279b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
280b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
281b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    @Override
282b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
283b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                            throws Throwable {
284b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        View view = new View(getContext());
285b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        view.setMinimumWidth(100);
286b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        view.setMinimumHeight(100);
287b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        assertTrue(mRecyclerView.isComputingLayout());
288b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        return new RecyclerView.ViewHolder(view) {};
289b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    }
290b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                });
291b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
292b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.setAdapter(mockAdapter);
293b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
294b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        layout(100, 100);
295b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
296b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        verify(mockAdapter, times(1)).onCreateViewHolder(mRecyclerView, 0);
297b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
298b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        // prefetch an item, should still observe isComputingLayout in that create
299b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
300b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
301b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
302b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        verify(mockAdapter, times(2)).onCreateViewHolder(mRecyclerView, 0);
303b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    }
304b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
305b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    @Test
306213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    public void prefetchAfterOrientationChange() {
307213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        LinearLayoutManager layout = new LinearLayoutManager(getContext(),
308213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                LinearLayoutManager.VERTICAL, false);
309213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.setLayoutManager(layout);
310213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
311213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        // 100x100 pixel views
312213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
3138a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            @NonNull
314213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
3158a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
3168a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
317213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                View view = new View(getContext());
318213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                view.setMinimumWidth(100);
319213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                view.setMinimumHeight(100);
320213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                assertTrue(mRecyclerView.isComputingLayout());
321213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                return new RecyclerView.ViewHolder(view) {};
322213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            }
323213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
324213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
3258a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {}
326213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
327213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
328213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            public int getItemCount() {
329213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                return 100;
330213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            }
331213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        });
332213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
333213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        layout(100, 100);
334213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
335213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        layout.setOrientation(LinearLayoutManager.HORIZONTAL);
336213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
337213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        // Prefetch an item after changing orientation, before layout - shouldn't crash
338213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(1, 1);
339213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
340213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    }
341213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
342213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    @Test
3430951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    public void prefetchDrag() {
3440951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // event dispatch requires a parent
3450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        ViewGroup parent = new FrameLayout(getContext());
3460951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        parent.addView(mRecyclerView);
3470951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3490951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.setLayoutManager(
3500951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
3510951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3520951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // 1000x1000 pixel views
3530951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
3540951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3558a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
3568a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
3570951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                mRecyclerView.registerTimePassingMs(5);
3580951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                View view = new View(getContext());
3590951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                view.setMinimumWidth(1000);
3600951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                view.setMinimumHeight(1000);
3610951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                return new RecyclerView.ViewHolder(view) {};
3620951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3630951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3640951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3658a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
3660951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                mRecyclerView.registerTimePassingMs(5);
3670951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3680951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3690951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3700951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            public int getItemCount() {
3710951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                return 100;
3720951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3730951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        };
3740951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.setAdapter(adapter);
3750951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3760951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        layout(1000, 1000);
3770951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3780951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        long time = SystemClock.uptimeMillis();
3790951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3800951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 500, 1000, 0));
3810951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3820951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3830951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3840951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3850951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // Consume slop
3860951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3870951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 500, 0));
3880951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3890951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 0,30
3900951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3910951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 470, 0));
3920951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3930951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(30, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3940951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3950951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 10,15
3960951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3970951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0));
3980951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3990951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
4000951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
4010951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 0,0 - IGNORED
4020951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
4030951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0));
4040951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); // same as prev
4050951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); // same as prev
4060951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    }
4070951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
4080951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    @Test
409a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    public void prefetchItemsRespectDeadline() {
410a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
411a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
412a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // 100x100 pixel views
413a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
414a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
4158a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
4168a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
417a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
418a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                View view = new View(getContext());
419a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumWidth(100);
420a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumHeight(100);
421945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return new RecyclerView.ViewHolder(view) {};
422a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
423a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
424a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
4258a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(
4268a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull RecyclerView.ViewHolder holder, int position) {
427a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
428a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
429a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
430a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
431a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public int getItemCount() {
432a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                return 100;
433a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
434a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        };
435a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setAdapter(adapter);
436a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
437dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 300);
438a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
439356880d3de117b067522ad452f4e3eed85ce444cChris Craik        // offset scroll so that no prefetch-able views are directly adjacent to viewport
440356880d3de117b067522ad452f4e3eed85ce444cChris Craik        mRecyclerView.scrollBy(0, 50);
441356880d3de117b067522ad452f4e3eed85ce444cChris Craik
442a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
443a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
444a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
445a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe
446a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19);
447a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
448a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Timed prefetch
449945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
45047d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
451a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
452a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // will have enough time to inflate/bind one view, and inflate another
453a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 1);
454a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
455356880d3de117b067522ad452f4e3eed85ce444cChris Craik        // Note: order/view below is an implementation detail
456356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12);
457a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
458a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
459a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Unbounded prefetch this time
46047d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
461a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
462a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should finish all work
463a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
464a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
465356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12, 13, 14);
466945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    }
467945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
468945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    @Test
4691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    public void partialPrefetchAvoidsViewRecycledCallback() {
4701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
4711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // 100x100 pixel views
4731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
4741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4758a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
4768a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
4771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView.registerTimePassingMs(5);
4781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                View view = new View(getContext());
4791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                view.setMinimumWidth(100);
4801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                view.setMinimumHeight(100);
4811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return new RecyclerView.ViewHolder(view) {};
4821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4858a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
4861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView.registerTimePassingMs(5);
4871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public int getItemCount() {
4911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return 100;
4921e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4958a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
4961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                // verify unbound view doesn't get
4971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                assertNotEquals(RecyclerView.NO_POSITION, holder.getAdapterPosition());
4981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4991e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        };
5001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(adapter);
5011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(100, 300);
5031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5041e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // offset scroll so that no prefetch-able views are directly adjacent to viewport
5051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.scrollBy(0, 50);
5061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5071e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
5081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
5091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // Should take 10 ms to inflate + bind, so just give it 9 so it doesn't have time to bind
5111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9);
5121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // Timed prefetch
5141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
5151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
5161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // will have enough time to inflate but not bind one view
5181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
5191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
5201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.ViewHolder pooledHolder = mRecyclerView.getRecycledViewPool()
5211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .mScrap.get(0).mScrapHeap.get(0);
5221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAdapterPosition());
5231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
5241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
526945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    public void prefetchStaggeredItemsPriority() {
527945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        StaggeredGridLayoutManager sglm =
528945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
529945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.setLayoutManager(sglm);
530945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
531945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // first view 50x100 pixels, rest are 100x100 so second column is offset
532945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
5338a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            @NonNull
534945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
5358a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
5368a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
537945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return new RecyclerView.ViewHolder(new View(getContext())) {};
538945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
539945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
540945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
5418a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
542945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                holder.itemView.setMinimumWidth(100);
543945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                holder.itemView.setMinimumHeight(position == 0 ? 50 : 100);
544945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
545945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
546945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
547945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            public int getItemCount() {
548945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return 100;
549945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
550945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        });
551945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
552dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 200);
553945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
554945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        /* Each row is 50 pixels:
555945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         * ------------- *
556945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   0   |   1   *
557945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   1   *
558945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   3   *
559945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___4___|___3___*
560945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   5   *
561945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   6   |   5   *
562945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *      ...      *
563945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         */
564945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.getChildCount());
565945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(0, sglm.getFirstChildPosition());
566945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(4, sglm.getLastChildPosition());
567945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
568945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching down shows 5 at 0 pixels away, 6 at 50 pixels away
569356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10,
570945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {5, 0}, new Integer[] {6, 50});
571945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
572945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // Prefetch upward shows nothing
573356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
574945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
575945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.scrollBy(0, 100);
576945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
577945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        /* Each row is 50 pixels:
578945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         * ------------- *
579945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   0   |   1   *
580945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___2___|___1___*
581945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   3   *
582945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   3   *
583945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   5   *
584945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___6___|___5___*
585945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   6   |   7   *
586945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   8   |   7   *
587945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *      ...      *
588945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         */
589945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
590945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.getChildCount());
591945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(2, sglm.getFirstChildPosition());
592945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(6, sglm.getLastChildPosition());
593945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
594945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching down shows 7 at 0 pixels away, 8 at 50 pixels away
595356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10,
596945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {7, 0}, new Integer[] {8, 50});
597945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
598945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching up shows 1 is 0 pixels away, 0 at 50 pixels away
599356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10,
600945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {1, 0}, new Integer[] {0, 50});
601945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    }
602dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
603dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    @Test
604695d9b8629fa048795298359d4845f0747364fafChris Craik    public void prefetchStaggeredPastBoundary() {
605695d9b8629fa048795298359d4845f0747364fafChris Craik        StaggeredGridLayoutManager sglm =
606695d9b8629fa048795298359d4845f0747364fafChris Craik                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
607695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.setLayoutManager(sglm);
608695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
6098a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            @NonNull
610695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
6118a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
6128a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
613695d9b8629fa048795298359d4845f0747364fafChris Craik                return new RecyclerView.ViewHolder(new View(getContext())) {};
614695d9b8629fa048795298359d4845f0747364fafChris Craik            }
615695d9b8629fa048795298359d4845f0747364fafChris Craik
616695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
6178a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
618695d9b8629fa048795298359d4845f0747364fafChris Craik                holder.itemView.setMinimumWidth(100);
619695d9b8629fa048795298359d4845f0747364fafChris Craik                holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
620695d9b8629fa048795298359d4845f0747364fafChris Craik            }
621695d9b8629fa048795298359d4845f0747364fafChris Craik
622695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
623695d9b8629fa048795298359d4845f0747364fafChris Craik            public int getItemCount() {
624695d9b8629fa048795298359d4845f0747364fafChris Craik                return 2;
625695d9b8629fa048795298359d4845f0747364fafChris Craik            }
626695d9b8629fa048795298359d4845f0747364fafChris Craik        });
627695d9b8629fa048795298359d4845f0747364fafChris Craik
628695d9b8629fa048795298359d4845f0747364fafChris Craik        layout(200, 100);
629695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.scrollBy(0, 50);
630695d9b8629fa048795298359d4845f0747364fafChris Craik
631695d9b8629fa048795298359d4845f0747364fafChris Craik        /* Each row is 50 pixels:
632695d9b8629fa048795298359d4845f0747364fafChris Craik         * ------------- *
633695d9b8629fa048795298359d4845f0747364fafChris Craik         *___0___|___1___*
634695d9b8629fa048795298359d4845f0747364fafChris Craik         *   0   |   1   *
635695d9b8629fa048795298359d4845f0747364fafChris Craik         *_______|___1___*
636695d9b8629fa048795298359d4845f0747364fafChris Craik         *       |   1   *
637695d9b8629fa048795298359d4845f0747364fafChris Craik         */
638695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(2, mRecyclerView.getChildCount());
639695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(0, sglm.getFirstChildPosition());
640695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(1, sglm.getLastChildPosition());
641695d9b8629fa048795298359d4845f0747364fafChris Craik
642695d9b8629fa048795298359d4845f0747364fafChris Craik        // prefetch upward gets nothing
643695d9b8629fa048795298359d4845f0747364fafChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
644695d9b8629fa048795298359d4845f0747364fafChris Craik
645695d9b8629fa048795298359d4845f0747364fafChris Craik        // prefetch downward gets nothing (and doesn't crash...)
646695d9b8629fa048795298359d4845f0747364fafChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
647695d9b8629fa048795298359d4845f0747364fafChris Craik    }
648695d9b8629fa048795298359d4845f0747364fafChris Craik
649695d9b8629fa048795298359d4845f0747364fafChris Craik    @Test
650dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    public void prefetchItemsSkipAnimations() {
651dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
652dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setLayoutManager(llm);
653dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        final int[] expandedPosition = new int[] {-1};
654dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
655dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        final RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
656dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
6578a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
6588a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
6591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return new RecyclerView.ViewHolder(new View(parent.getContext())) {};
660dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
661dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
662dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
6638a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
664dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                int height = expandedPosition[0] == position ? 400 : 100;
665dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(200, height));
666dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
667dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
668dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
669dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            public int getItemCount() {
670dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                return 10;
671dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
672dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        };
673dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
674dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // make move duration long enough to be able to see the effects
675dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
676dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        itemAnimator.setMoveDuration(10000);
677dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setAdapter(adapter);
678dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
679dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
680dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
681dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        expandedPosition[0] = 1;
682dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // insert payload to avoid creating a new view
683dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        adapter.notifyItemChanged(1, new Object());
684dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
685dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
686dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
687dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
688dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(itemAnimator.isRunning());
689dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(2, llm.getChildCount());
690dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(4, mRecyclerView.getChildCount());
691dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
692dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // animating view should be observable as hidden, uncached...
693dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2);
694dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertNotNull("Animating view should be found, hidden",
695dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                mRecyclerView.mChildHelper.findHiddenNonRemovedView(2));
696dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2));
697dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
698dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // ...but must not be removed for prefetch
699dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
700dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
701dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
702dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals("Prefetch must target one view", 1, mRecyclerView.mPrefetchRegistry.mCount);
703dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        int prefetchTarget = mRecyclerView.mPrefetchRegistry.mPrefetchArray[0];
704dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals("Prefetch must target view 2", 2, prefetchTarget);
705dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
706dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // animating view still observable as hidden, uncached
707dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2);
708dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertNotNull("Animating view should be found, hidden",
709dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                mRecyclerView.mChildHelper.findHiddenNonRemovedView(2));
710dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2));
711dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
712dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(itemAnimator.isRunning());
713dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(2, llm.getChildCount());
714dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(4, mRecyclerView.getChildCount());
715dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    }
7161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
7181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    public void viewHolderFindsNestedRecyclerViews() {
7191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
7201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(llm);
7211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
7231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
7241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
7251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
7261e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
7271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                            throws Throwable {
7281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        View view = new RecyclerView(getContext());
7291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        view.setLayoutParams(new RecyclerView.LayoutParams(100, 100));
7301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        return new RecyclerView.ViewHolder(view) {};
7311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    }
7321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                });
7331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
7341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(mockAdapter);
7351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(100, 200);
7371e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7381e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        verify(mockAdapter, times(2)).onCreateViewHolder(any(ViewGroup.class), anyInt());
7391e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        verify(mockAdapter, times(2)).onBindViewHolder(
740963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas                argThat(new ArgumentMatcher<RecyclerView.ViewHolder>() {
7411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
742963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas                    public boolean matches(RecyclerView.ViewHolder holder) {
7438123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik                        return holder.itemView == holder.mNestedRecyclerView.get();
7441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    }
7451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                }),
7461e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                anyInt(),
7471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                any(List.class));
7481e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
7491e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7500fab45107f043a4856aad2e1fd785b396898616eChris Craik    class InnerAdapter extends RecyclerView.Adapter<InnerAdapter.ViewHolder> {
7510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        private static final int INNER_ITEM_COUNT = 20;
752f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        int mItemsBound = 0;
753f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
7540fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
7551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            ViewHolder(View itemView) {
7561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                super(itemView);
7571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        InnerAdapter() {}
7611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7638a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
7640fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
7651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            View view = new View(parent.getContext());
7661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            view.setLayoutParams(new RecyclerView.LayoutParams(100, 100));
7671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return new ViewHolder(view);
7681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7718a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
7720fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
773f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik            mItemsBound++;
774f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        }
7751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public int getItemCount() {
7781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return INNER_ITEM_COUNT;
7791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
7811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7820fab45107f043a4856aad2e1fd785b396898616eChris Craik    class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
7831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7840ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        private boolean mReverseInner;
7850ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
7860fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
7871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            private final RecyclerView mRecyclerView;
7881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            ViewHolder(RecyclerView itemView) {
7891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                super(itemView);
7901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView = itemView;
7911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7921e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        ArrayList<InnerAdapter> mAdapters = new ArrayList<>();
7950ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        ArrayList<Parcelable> mSavedStates = new ArrayList<>();
7961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
7971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        OuterAdapter() {
7990ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            this(false);
8000ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        }
8010ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8020ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        OuterAdapter(boolean reverseInner) {
8038123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            this(reverseInner, 10);
8048123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
8058123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
8068123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        OuterAdapter(boolean reverseInner, int itemCount) {
8070ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            mReverseInner = reverseInner;
8088123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            for (int i = 0; i < itemCount; i++) {
8091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mAdapters.add(new InnerAdapter());
8100ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                mSavedStates.add(null);
8111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
8121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8148123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        void addItem() {
8158123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            int index = getItemCount();
8168123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            mAdapters.add(new InnerAdapter());
8178123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            mSavedStates.add(null);
8188123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            notifyItemInserted(index);
8198123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
8208123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
8211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8228a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
8230fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
8240fab45107f043a4856aad2e1fd785b396898616eChris Craik
825bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            RecyclerView rv = new RecyclerView(parent.getContext()) {
826bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                @Override
827bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                public int getWindowVisibility() {
828bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                    // Pretend to be visible to avoid being filtered out
829bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                    return View.VISIBLE;
830bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                }
831bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            };
8321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
8330ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                    LinearLayoutManager.HORIZONTAL, mReverseInner));
8341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setRecycledViewPool(mSharedPool);
8351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100));
8361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return new ViewHolder(rv);
8371e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8381e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8391e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8408a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
8410fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
8420fab45107f043a4856aad2e1fd785b396898616eChris Craik
8430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            // Tests may rely on bound holders not being shared between inner adapters,
8440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            // since we force recycle here
8450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            holder.mRecyclerView.swapAdapter(mAdapters.get(position), true);
8460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            Parcelable savedState = mSavedStates.get(position);
8480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            if (savedState != null) {
8490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
8500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                mSavedStates.set(position, null);
8510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            }
8520ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        }
8530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        @Override
8558a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onViewRecycled(@NonNull ViewHolder holder) {
8560ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            mSavedStates.set(holder.getAdapterPosition(),
8570ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                    holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
8581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public int getItemCount() {
8628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            return mAdapters.size();
8631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
8651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
8670ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchSimple() {
8681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
869d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik        assertEquals(2, llm.getInitialPrefetchItemCount());
8701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(llm);
8721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(new OuterAdapter());
8731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(200, 200);
8751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
8761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // prefetch 2 (default)
8781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
8801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertNotNull(holder);
8811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertNotNull(holder.mNestedRecyclerView);
8828123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        RecyclerView innerView = holder.mNestedRecyclerView.get();
8838123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
8841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // prefetch 4
8868123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        ((LinearLayoutManager) innerView.getLayoutManager())
8871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .setInitialPrefetchItemCount(4);
8881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8898123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3);
8901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
8910ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8920ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    @Test
8930fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    public void nestedPrefetchNotClearInnerStructureChangeFlag() {
8940fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        LinearLayoutManager llm = new LinearLayoutManager(getContext());
8950fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertEquals(2, llm.getInitialPrefetchItemCount());
8960fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8970fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.setLayoutManager(llm);
8980fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.setAdapter(new OuterAdapter());
8990fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9000fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
9010fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
9020fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9030fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch 2 (default)
9040fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9050fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9060fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertNotNull(holder);
9070fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertNotNull(holder.mNestedRecyclerView);
9080fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView innerView = holder.mNestedRecyclerView.get();
9090fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView.Adapter innerAdapter = innerView.getAdapter();
9100fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
9110fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // mStructureChanged is initially true before first layout pass.
9120fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
9130fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
9140fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9150fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // layout position 2 and clear mStructureChanged
9160fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.scrollToPosition(2);
9170fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
9180fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.scrollToPosition(0);
9190fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
9200fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertFalse(innerView.mState.mStructureChanged);
9210fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertFalse(innerView.hasPendingAdapterUpdates());
9220fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // notify change on the cached innerView.
9240fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        innerAdapter.notifyDataSetChanged();
9250fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
9260fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
9270fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9280fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch again
9290fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
9300fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        ((LinearLayoutManager) innerView.getLayoutManager())
9310fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu                .setInitialPrefetchItemCount(2);
9320fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9330fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
9340fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9350fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // The re-prefetch is not necessary get the same inner view but we will get same Adapter
9360fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9370fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        innerView = holder.mNestedRecyclerView.get();
9380fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertSame(innerAdapter, innerView.getAdapter());
9390fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch shouldn't clear the mStructureChanged flag
9400fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
9410fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
9420fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    }
9430fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9440fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    @Test
9450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchReverseInner() {
9460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setAdapter(new OuterAdapter(/* reverseInner = */ true));
9480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        layout(200, 200);
9500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
9510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9520ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9550ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // anchor from right side, should see last two positions
9568123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19);
9570ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    }
9580ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9590ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    @Test
9600ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchOffset() {
9610ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9620ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setAdapter(new OuterAdapter());
9630ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        layout(200, 200);
9650ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9660ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // Scroll top row by 5.5 items, verify positions 5, 6, 7 showing
9670ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        RecyclerView inner = (RecyclerView) mRecyclerView.getChildAt(0);
9680ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        inner.scrollBy(550, 0);
9690ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(5, RecyclerView.getChildViewHolderInt(inner.getChildAt(0)).mPosition);
9700ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(6, RecyclerView.getChildViewHolderInt(inner.getChildAt(1)).mPosition);
9710ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(7, RecyclerView.getChildViewHolderInt(inner.getChildAt(2)).mPosition);
9720ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9730ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // scroll down 4 rows, up 3 so row 0 is adjacent but uncached
9740ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.scrollBy(0, 400);
9750ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.scrollBy(0, -300);
9760ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9770ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // top row no longer present
9780ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 0);
9790ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9800ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // prefetch upward, and validate that we've gotten the top row with correct offsets
9810ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
9820ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9830ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        inner = (RecyclerView) CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0).itemView;
9840ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6);
9850ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9860ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // prefetch 4
9870ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        ((LinearLayoutManager) inner.getLayoutManager()).setInitialPrefetchItemCount(4);
9880ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9890ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6, 7, 8);
9900ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    }
991f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
992f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    @Test
993f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    public void nestedPrefetchNotReset() {
994f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
995f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        OuterAdapter outerAdapter = new OuterAdapter();
996f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.setAdapter(outerAdapter);
997f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
998f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        layout(200, 200);
999f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
1000f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1001f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
1002f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // prefetch row 2, items 0 & 1
1003f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound);
1004f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1005f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
10068123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
1007f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
1008f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertNotNull(innerRecyclerView);
1009f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1);
1010f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound);
1011f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
1012f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // new row comes on, triggers layout...
1013f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.scrollBy(0, 50);
1014f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
1015f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // ... which shouldn't require new items to be bound,
1016f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // as prefetch has already done that work
1017f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound);
1018f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    }
10190fab45107f043a4856aad2e1fd785b396898616eChris Craik
10200fab45107f043a4856aad2e1fd785b396898616eChris Craik    static void validateRvChildrenValid(RecyclerView recyclerView, int childCount) {
10210fab45107f043a4856aad2e1fd785b396898616eChris Craik        ChildHelper childHelper = recyclerView.mChildHelper;
10220fab45107f043a4856aad2e1fd785b396898616eChris Craik
10230fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(childCount, childHelper.getUnfilteredChildCount());
10240fab45107f043a4856aad2e1fd785b396898616eChris Craik        for (int i = 0; i < childHelper.getUnfilteredChildCount(); i++) {
10250fab45107f043a4856aad2e1fd785b396898616eChris Craik            assertFalse(recyclerView.getChildViewHolder(
10260fab45107f043a4856aad2e1fd785b396898616eChris Craik                    childHelper.getUnfilteredChildAt(i)).isInvalid());
10270fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
10280fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
10290fab45107f043a4856aad2e1fd785b396898616eChris Craik
10300fab45107f043a4856aad2e1fd785b396898616eChris Craik    @Test
10310fab45107f043a4856aad2e1fd785b396898616eChris Craik    public void nestedPrefetchCacheNotTouched() {
10320fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10330fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterAdapter outerAdapter = new OuterAdapter();
10340fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setAdapter(outerAdapter);
10350fab45107f043a4856aad2e1fd785b396898616eChris Craik
10360fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 200);
10370fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.scrollBy(0, 100);
10380fab45107f043a4856aad2e1fd785b396898616eChris Craik
10390fab45107f043a4856aad2e1fd785b396898616eChris Craik        // item 0 is cached
10400fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
10410fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0);
10428123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
10430fab45107f043a4856aad2e1fd785b396898616eChris Craik
10440fab45107f043a4856aad2e1fd785b396898616eChris Craik        // try and prefetch it
10450fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
10460fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
10470fab45107f043a4856aad2e1fd785b396898616eChris Craik
10480fab45107f043a4856aad2e1fd785b396898616eChris Craik        // make sure cache's inner items aren't rebound unnecessarily
10490fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
10508123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
10518123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    }
10528123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    @Test
10548123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    public void nestedRemoveAnimatingView() {
10558123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10568123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        OuterAdapter outerAdapter = new OuterAdapter(false, 1);
10578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.setAdapter(outerAdapter);
10588123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30));
10598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10608123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        layout(200, 200);
10618123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // Insert 3 items - only first one in viewport, so only it animates
10638123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        for (int i = 0; i < 3; i++) {
10648123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            outerAdapter.addItem();
10658123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
10668123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        layout(200, 200); // layout again to kick off animation
10678123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10688123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10698123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // item 1 is animating, so scroll it out of viewport
10708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.scrollBy(0, 200);
10718123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10728123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for...
10738123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
10748123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(1, mRecycler.mCachedViews.size());
10758123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0);
10768123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
10778123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10788123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // until animation ends
10798123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.getItemAnimator().endAnimations();
10808123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
10818123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecycler.mCachedViews.size());
10828123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1);
10838123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
10848123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10858123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) {
10868123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            assertNotNull(viewHolder.mNestedRecyclerView);
10878123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
10880fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
10890fab45107f043a4856aad2e1fd785b396898616eChris Craik
10909ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    @Test
10919ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    public void nestedExpandCacheCorrectly() {
10929ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE;
10939ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10949ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10959ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        OuterAdapter outerAdapter = new OuterAdapter();
10969ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.setAdapter(outerAdapter);
10979ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10989ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        layout(200, 200);
10999ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11009ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
11019ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
11029ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11039ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        // after initial prefetch, view cache max expanded by number of inner items prefetched (2)
11049ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
11059ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        RecyclerView innerView = holder.mNestedRecyclerView.get();
11069ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
11079ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved);
11089ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
11099ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11109ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        try {
11119ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // Note: As a hack, we not only must manually dispatch attachToWindow(), but we
11129ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker
11139ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // is registered to the wrong thread, since @setup is called on a different thread
11149ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // from @Test. Assert this, so this test can be fixed when setup == test thread.
11159ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size());
11169ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.isAttachedToWindow());
11179ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.onAttachedToWindow();
11189ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11199ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // bring prefetch view into viewport, at which point it shouldn't have cache expanded...
11209ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            mRecyclerView.scrollBy(0, 100);
11219ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
11229ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved);
11239ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
11249ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11259ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // until a valid horizontal prefetch caches an item, and expands view count by one
11269ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.mPrefetchRegistry.setPrefetchVector(1, 0);
11279ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker
11289ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
11299ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved);
11309ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
11319ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        } finally {
11329ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            if (innerView.isAttachedToWindow()) {
11339ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik                innerView.onDetachedFromWindow();
11349ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            }
11359ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        }
11369ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    }
11379ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11380fab45107f043a4856aad2e1fd785b396898616eChris Craik    /**
11390fab45107f043a4856aad2e1fd785b396898616eChris Craik     * Similar to OuterAdapter above, but uses notifyDataSetChanged() instead of set/swapAdapter
11400fab45107f043a4856aad2e1fd785b396898616eChris Craik     * to update data for the inner RecyclerViews when containing ViewHolder is bound.
11410fab45107f043a4856aad2e1fd785b396898616eChris Craik     */
11420fab45107f043a4856aad2e1fd785b396898616eChris Craik    class OuterNotifyAdapter extends RecyclerView.Adapter<OuterNotifyAdapter.ViewHolder> {
11430fab45107f043a4856aad2e1fd785b396898616eChris Craik        private static final int OUTER_ITEM_COUNT = 10;
11440fab45107f043a4856aad2e1fd785b396898616eChris Craik
11450fab45107f043a4856aad2e1fd785b396898616eChris Craik        private boolean mReverseInner;
11460fab45107f043a4856aad2e1fd785b396898616eChris Craik
11470fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
11480fab45107f043a4856aad2e1fd785b396898616eChris Craik            private final RecyclerView mRecyclerView;
11490fab45107f043a4856aad2e1fd785b396898616eChris Craik            private final InnerAdapter mAdapter;
11500fab45107f043a4856aad2e1fd785b396898616eChris Craik            ViewHolder(RecyclerView itemView) {
11510fab45107f043a4856aad2e1fd785b396898616eChris Craik                super(itemView);
11520fab45107f043a4856aad2e1fd785b396898616eChris Craik                mRecyclerView = itemView;
11530fab45107f043a4856aad2e1fd785b396898616eChris Craik                mAdapter = new InnerAdapter();
11540fab45107f043a4856aad2e1fd785b396898616eChris Craik                mRecyclerView.setAdapter(mAdapter);
11550fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11560fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11570fab45107f043a4856aad2e1fd785b396898616eChris Craik
11580fab45107f043a4856aad2e1fd785b396898616eChris Craik        ArrayList<Parcelable> mSavedStates = new ArrayList<>();
11590fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
11600fab45107f043a4856aad2e1fd785b396898616eChris Craik
11610fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter() {
11620fab45107f043a4856aad2e1fd785b396898616eChris Craik            this(false);
11630fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11640fab45107f043a4856aad2e1fd785b396898616eChris Craik
11650fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter(boolean reverseInner) {
11660fab45107f043a4856aad2e1fd785b396898616eChris Craik            mReverseInner = reverseInner;
11670fab45107f043a4856aad2e1fd785b396898616eChris Craik            for (int i = 0; i <= OUTER_ITEM_COUNT; i++) {
11680fab45107f043a4856aad2e1fd785b396898616eChris Craik                mSavedStates.add(null);
11690fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11700fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11710fab45107f043a4856aad2e1fd785b396898616eChris Craik
11728a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        @NonNull
11730fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11748a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
11750fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
11760fab45107f043a4856aad2e1fd785b396898616eChris Craik            RecyclerView rv = new RecyclerView(parent.getContext());
11770fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
11780fab45107f043a4856aad2e1fd785b396898616eChris Craik                    LinearLayoutManager.HORIZONTAL, mReverseInner));
11790fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setRecycledViewPool(mSharedPool);
11800fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100));
11810fab45107f043a4856aad2e1fd785b396898616eChris Craik            return new ViewHolder(rv);
11820fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11830fab45107f043a4856aad2e1fd785b396898616eChris Craik
11840fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11858a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
11860fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
11870fab45107f043a4856aad2e1fd785b396898616eChris Craik            // if we had actual data to put into our adapter, this is where we'd do it...
11880fab45107f043a4856aad2e1fd785b396898616eChris Craik
11890fab45107f043a4856aad2e1fd785b396898616eChris Craik            // ... then notify the adapter that it has new content:
11900fab45107f043a4856aad2e1fd785b396898616eChris Craik            holder.mAdapter.notifyDataSetChanged();
11910fab45107f043a4856aad2e1fd785b396898616eChris Craik
11920fab45107f043a4856aad2e1fd785b396898616eChris Craik            Parcelable savedState = mSavedStates.get(position);
11930fab45107f043a4856aad2e1fd785b396898616eChris Craik            if (savedState != null) {
11940fab45107f043a4856aad2e1fd785b396898616eChris Craik                holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
11950fab45107f043a4856aad2e1fd785b396898616eChris Craik                mSavedStates.set(position, null);
11960fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11970fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11980fab45107f043a4856aad2e1fd785b396898616eChris Craik
11990fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
12008a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onViewRecycled(@NonNull ViewHolder holder) {
1201570ab881350c836743247140a0953f87acc21c8dChris Craik            if (holder.getAdapterPosition() >= 0) {
1202570ab881350c836743247140a0953f87acc21c8dChris Craik                mSavedStates.set(holder.getAdapterPosition(),
1203570ab881350c836743247140a0953f87acc21c8dChris Craik                        holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
1204570ab881350c836743247140a0953f87acc21c8dChris Craik            }
12050fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
12060fab45107f043a4856aad2e1fd785b396898616eChris Craik
12070fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
12080fab45107f043a4856aad2e1fd785b396898616eChris Craik        public int getItemCount() {
12090fab45107f043a4856aad2e1fd785b396898616eChris Craik            return OUTER_ITEM_COUNT;
12100fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
12110fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
12120fab45107f043a4856aad2e1fd785b396898616eChris Craik
12130fab45107f043a4856aad2e1fd785b396898616eChris Craik    @Test
12140fab45107f043a4856aad2e1fd785b396898616eChris Craik    public void nestedPrefetchDiscardStaleChildren() {
12150fab45107f043a4856aad2e1fd785b396898616eChris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
1216d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik        assertEquals(2, llm.getInitialPrefetchItemCount());
12170fab45107f043a4856aad2e1fd785b396898616eChris Craik
12180fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setLayoutManager(llm);
12190fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter();
12200fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setAdapter(outerAdapter);
12210fab45107f043a4856aad2e1fd785b396898616eChris Craik
12220fab45107f043a4856aad2e1fd785b396898616eChris Craik        // zero cache, so item we prefetch can't already be ready
12230fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setItemViewCacheSize(0);
12240fab45107f043a4856aad2e1fd785b396898616eChris Craik
12250fab45107f043a4856aad2e1fd785b396898616eChris Craik        // layout 3 items, then resize to 2...
12260fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 300);
12270fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 200);
12280fab45107f043a4856aad2e1fd785b396898616eChris Craik
12290fab45107f043a4856aad2e1fd785b396898616eChris Craik        // so 1 item is evicted into the RecycledViewPool (bypassing cache)
12300fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(1, mRecycler.mRecyclerPool.getRecycledViewCount(0));
12310fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(0, mRecycler.mCachedViews.size());
12320fab45107f043a4856aad2e1fd785b396898616eChris Craik
12330fab45107f043a4856aad2e1fd785b396898616eChris Craik        // This is a simple imitation of other behavior (namely, varied types in the outer adapter)
12340fab45107f043a4856aad2e1fd785b396898616eChris Craik        // that results in the same initial state to test: items in the pool with attached children
12350fab45107f043a4856aad2e1fd785b396898616eChris Craik        for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) {
12360fab45107f043a4856aad2e1fd785b396898616eChris Craik            // verify that children are attached and valid, since the RVs haven't been rebound
12370fab45107f043a4856aad2e1fd785b396898616eChris Craik            assertNotNull(holder.mNestedRecyclerView);
12388123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout);
12398123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
12400fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
12410fab45107f043a4856aad2e1fd785b396898616eChris Craik
12420fab45107f043a4856aad2e1fd785b396898616eChris Craik        // prefetch the outer item bind, but without enough time to do any inner binds
12430fab45107f043a4856aad2e1fd785b396898616eChris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9);
12440fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
12450fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
12460fab45107f043a4856aad2e1fd785b396898616eChris Craik
12470fab45107f043a4856aad2e1fd785b396898616eChris Craik        // 2 is prefetched without children
12480fab45107f043a4856aad2e1fd785b396898616eChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 2);
12490fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
12500fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertNotNull(holder);
12510fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertNotNull(holder.mNestedRecyclerView);
12528123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount());
12538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size());
12540fab45107f043a4856aad2e1fd785b396898616eChris Craik
12550fab45107f043a4856aad2e1fd785b396898616eChris Craik        // but if we give it more time to bind items, it'll now acquire its inner items
12560fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
12578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1);
12580fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
1259570ab881350c836743247140a0953f87acc21c8dChris Craik
1260570ab881350c836743247140a0953f87acc21c8dChris Craik
1261570ab881350c836743247140a0953f87acc21c8dChris Craik    @Test
1262570ab881350c836743247140a0953f87acc21c8dChris Craik    public void nestedPrefetchDiscardStalePrefetch() {
1263570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
1264570ab881350c836743247140a0953f87acc21c8dChris Craik        OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter();
1265570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setAdapter(outerAdapter);
1266570ab881350c836743247140a0953f87acc21c8dChris Craik
1267570ab881350c836743247140a0953f87acc21c8dChris Craik        // zero cache, so item we prefetch can't already be ready
1268570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setItemViewCacheSize(0);
1269570ab881350c836743247140a0953f87acc21c8dChris Craik
1270570ab881350c836743247140a0953f87acc21c8dChris Craik        // layout as 2x2, starting on row index 2, with empty cache
1271570ab881350c836743247140a0953f87acc21c8dChris Craik        layout(200, 200);
1272570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.scrollBy(0, 200);
1273570ab881350c836743247140a0953f87acc21c8dChris Craik
1274570ab881350c836743247140a0953f87acc21c8dChris Craik        // no views cached, or previously used (so we can trust number in mItemsBound)
1275570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecycler.mRecyclerPool.clear();
1276570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, mRecycler.mRecyclerPool.getRecycledViewCount(0));
1277570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, mRecycler.mCachedViews.size());
1278570ab881350c836743247140a0953f87acc21c8dChris Craik
1279570ab881350c836743247140a0953f87acc21c8dChris Craik        // prefetch the outer item and its inner children
1280570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1281570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1282570ab881350c836743247140a0953f87acc21c8dChris Craik
1283570ab881350c836743247140a0953f87acc21c8dChris Craik        // 4 is prefetched with 2 inner children, first two binds
1284570ab881350c836743247140a0953f87acc21c8dChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4);
1285570ab881350c836743247140a0953f87acc21c8dChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4);
1286570ab881350c836743247140a0953f87acc21c8dChris Craik        assertNotNull(holder);
1287570ab881350c836743247140a0953f87acc21c8dChris Craik        assertNotNull(holder.mNestedRecyclerView);
1288570ab881350c836743247140a0953f87acc21c8dChris Craik        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
1289570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount());
1290570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size());
1291570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound);
1292570ab881350c836743247140a0953f87acc21c8dChris Craik
1293570ab881350c836743247140a0953f87acc21c8dChris Craik        // notify data set changed, so any previously prefetched items invalid, and re-prefetch
1294570ab881350c836743247140a0953f87acc21c8dChris Craik        innerRecyclerView.getAdapter().notifyDataSetChanged();
1295570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1296570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1297570ab881350c836743247140a0953f87acc21c8dChris Craik
1298570ab881350c836743247140a0953f87acc21c8dChris Craik        // 4 is prefetched again...
1299570ab881350c836743247140a0953f87acc21c8dChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4);
1300570ab881350c836743247140a0953f87acc21c8dChris Craik
1301570ab881350c836743247140a0953f87acc21c8dChris Craik        // reusing the same instance with 2 inner children...
1302570ab881350c836743247140a0953f87acc21c8dChris Craik        assertSame(holder, CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4));
1303570ab881350c836743247140a0953f87acc21c8dChris Craik        assertSame(innerRecyclerView, holder.mNestedRecyclerView.get());
1304570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount());
1305570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size());
1306570ab881350c836743247140a0953f87acc21c8dChris Craik
1307570ab881350c836743247140a0953f87acc21c8dChris Craik        // ... but there should be two new binds
1308570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(4, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound);
1309570ab881350c836743247140a0953f87acc21c8dChris Craik    }
1310c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard
1311c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard    @Test
1312c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard    public void setRecycledViewPool_followedByTwoSetAdapters_clearsRecycledViewPool() {
1313c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        RecyclerView.ViewHolder viewHolder = new RecyclerView.ViewHolder(new View(getContext())) {};
1314c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        viewHolder.mItemViewType = 123;
1315c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        RecyclerView.Adapter adapter = mock(RecyclerView.Adapter.class);
1316c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        RecyclerView recyclerView = new RecyclerView(getContext());
1317c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
1318c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        recycledViewPool.putRecycledView(viewHolder);
1319c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard
1320c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        recyclerView.setRecycledViewPool(recycledViewPool);
1321c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        recyclerView.setAdapter(adapter);
1322c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        recyclerView.setAdapter(adapter);
1323c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard
1324c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        assertThat(recycledViewPool.getRecycledViewCount(123), is(equalTo(0)));
1325c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard    }
1326c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard
1327c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard    @Test
1328c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard    public void setRecycledViewPool_followedByTwoSwapAdapters_doesntClearRecycledViewPool() {
1329c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        RecyclerView.ViewHolder viewHolder = new RecyclerView.ViewHolder(new View(getContext())) {};
1330c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        viewHolder.mItemViewType = 123;
1331c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        RecyclerView.Adapter adapter = mock(RecyclerView.Adapter.class);
1332c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        RecyclerView recyclerView = new RecyclerView(getContext());
1333c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
1334c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        recycledViewPool.putRecycledView(viewHolder);
1335c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard
1336c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        recyclerView.setRecycledViewPool(recycledViewPool);
1337c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        recyclerView.swapAdapter(adapter, false);
1338c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        recyclerView.swapAdapter(adapter, false);
1339c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard
1340c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard        assertThat(recycledViewPool.getRecycledViewCount(123), is(equalTo(1)));
1341c76578ea4138aa224f8142a5de111ff38b79d9c3shepshapard    }
1342945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik}
1343