RecyclerViewCacheTest.java revision c95a6f1f125ad3a7e1f9f79bccf4b2603bc40eba
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
19945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craikimport static org.junit.Assert.assertEquals;
200fab45107f043a4856aad2e1fd785b396898616eChris Craikimport static org.junit.Assert.assertFalse;
211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.junit.Assert.assertNotEquals;
22dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craikimport static org.junit.Assert.assertNotNull;
230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Guimport static org.junit.Assert.assertSame;
24ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue;
25ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any;
26ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt;
271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.mockito.Matchers.argThat;
28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock;
29ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never;
30ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times;
31ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify;
32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when;
33ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas
3436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context;
35e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build;
360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craikimport android.os.Parcelable;
370951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.os.SystemClock;
3836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry;
39ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress;
40754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.SmallTest;
4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4;
420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.view.MotionEvent;
4336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View;
4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup;
450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.widget.FrameLayout;
4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
47c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull;
48c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
49356880d3de117b067522ad452f4e3eed85ce444cChris Craikimport org.junit.After;
5036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before;
5136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test;
5236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith;
53963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikasimport org.mockito.ArgumentMatcher;
5436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock;
5536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer;
5636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport java.util.ArrayList;
5836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List;
59a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit;
6036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
6136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest
62e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
6336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class)
6436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest {
65a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    TimeMockingRecyclerView mRecyclerView;
6636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    RecyclerView.Recycler mRecycler;
6736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    private class TimeMockingRecyclerView extends RecyclerView {
69a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        private long mMockNanoTime = 0;
70a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
71a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        TimeMockingRecyclerView(Context context) {
72a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            super(context);
73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
74a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
75a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        public void registerTimePassingMs(long ms) {
76a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms);
77a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
78a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
79a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        @Override
80a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        long getNanoTime() {
81a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            return mMockNanoTime;
82a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
83bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik
84bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        @Override
85bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        public int getWindowVisibility() {
86bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            // Pretend to be visible to avoid being filtered out
87bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            return View.VISIBLE;
88bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        }
89356880d3de117b067522ad452f4e3eed85ce444cChris Craik    }
90a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
9136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Before
92356880d3de117b067522ad452f4e3eed85ce444cChris Craik    public void setup() throws Exception {
93a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView = new TimeMockingRecyclerView(getContext());
94356880d3de117b067522ad452f4e3eed85ce444cChris Craik        mRecyclerView.onAttachedToWindow();
9536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler = mRecyclerView.mRecycler;
9636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
9736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
98356880d3de117b067522ad452f4e3eed85ce444cChris Craik    @After
99356880d3de117b067522ad452f4e3eed85ce444cChris Craik    public void teardown() throws Exception {
100356880d3de117b067522ad452f4e3eed85ce444cChris Craik        if (mRecyclerView.isAttachedToWindow()) {
101356880d3de117b067522ad452f4e3eed85ce444cChris Craik            mRecyclerView.onDetachedFromWindow();
102356880d3de117b067522ad452f4e3eed85ce444cChris Craik        }
103356880d3de117b067522ad452f4e3eed85ce444cChris Craik        GapWorker gapWorker = GapWorker.sGapWorker.get();
104356880d3de117b067522ad452f4e3eed85ce444cChris Craik        if (gapWorker != null) {
105356880d3de117b067522ad452f4e3eed85ce444cChris Craik            assertTrue(gapWorker.mRecyclerViews.isEmpty());
106356880d3de117b067522ad452f4e3eed85ce444cChris Craik        }
107356880d3de117b067522ad452f4e3eed85ce444cChris Craik    }
108356880d3de117b067522ad452f4e3eed85ce444cChris Craik
10936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    private Context getContext() {
11036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        return InstrumentationRegistry.getContext();
11136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
11236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
113dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    private void layout(int width, int height) {
114dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.measure(
115dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
116dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
117dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.layout(0, 0, width, height);
118dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    }
119dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
12036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
12136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchReusesCacheItems() {
12236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() {
12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        ViewGroup.LayoutParams.WRAP_CONTENT);
12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
12836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
12936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
1301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
1313104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik                    LayoutPrefetchRegistry prefetchManager) {
132945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(0, 0);
133945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(1, 0);
134945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(2, 0);
13536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
13636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
13736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        };
14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(prefetchingLayoutManager);
14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(new View(getContext())) {};
15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(10);
15336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
15436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
155dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(320, 320);
15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt());
15836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onBindViewHolder(
15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Prefetch multiple times...
16336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        for (int i = 0; i < 4; i++) {
16447d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik            mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            // ...but should only see the same three items fetched/bound once each
16736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt());
16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onBindViewHolder(
16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
17036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            assertTrue(mRecycler.mCachedViews.size() == 3);
1721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 0, 1, 2);
17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
17536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
17736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedWithInserts() {
178dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
18136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
18436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
18536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
186dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        View view = new View(getContext());
187dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        view.setMinimumWidth(100);
188dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        view.setMinimumHeight(100);
189dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        return new RecyclerView.ViewHolder(view) {};
19036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
19136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
19236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
19336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
19436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
195dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 100);
19636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
197945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(2, mRecyclerView.mRecycler.mViewCacheMax);
198dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
19947d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
200945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.mRecycler.mViewCacheMax);
201945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
2021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 3, 4, 5);
20336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // further views recycled, as though from scrolling, shouldn't evict prefetched views:
20536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(10));
206dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10);
20736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(20));
209dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10, 20);
21036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(30));
212dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 20, 30);
21336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(40));
215dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 30, 40);
21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // After clearing the cache, the prefetch priorities should be cleared as well:
21836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.mRecycler.recycleAndClearCachedViews();
219dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        for (int i : new int[] {3, 4, 5, 50, 60, 70, 80, 90}) {
22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            mRecycler.recycleView(mRecycler.getViewForPosition(i));
22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
22236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // cache only contains most recent positions, no priority for previous prefetches:
224356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 50, 60, 70, 80, 90);
22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedOnScroll() {
22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // 100x100 pixel views
23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
23436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
23536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
23636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        View view = new View(getContext());
23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumWidth(100);
24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumHeight(100);
24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(view) {};
24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // NOTE: requested cache size must be smaller than span count so two rows cannot fit
24836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setItemViewCacheSize(2);
24936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
250dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 150);
25136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 75);
25236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
25336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
25436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // rows 0, 1, and 2 are all attached and visible. Prefetch row 3:
255945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
25647d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
25736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
25836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is cached:
259356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11);
26036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
26136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Scroll so 1 falls off (though 3 is still not on screen)
26336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 50);
26436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is still cached, with a couple other recycled views:
266356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11);
26736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 5);
26836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
269a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
270a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    @Test
271b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    public void prefetchIsComputingLayout() {
272b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
273b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
274b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        // 100x100 pixel views
275b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
276b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
277b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
278b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    @Override
279b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
280b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                            throws Throwable {
281b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        View view = new View(getContext());
282b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        view.setMinimumWidth(100);
283b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        view.setMinimumHeight(100);
284b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        assertTrue(mRecyclerView.isComputingLayout());
285b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        return new RecyclerView.ViewHolder(view) {};
286b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    }
287b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                });
288b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
289b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.setAdapter(mockAdapter);
290b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
291b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        layout(100, 100);
292b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
293b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        verify(mockAdapter, times(1)).onCreateViewHolder(mRecyclerView, 0);
294b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
295b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        // prefetch an item, should still observe isComputingLayout in that create
296b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
297b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
298b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
299b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        verify(mockAdapter, times(2)).onCreateViewHolder(mRecyclerView, 0);
300b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    }
301b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
302b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    @Test
303213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    public void prefetchAfterOrientationChange() {
304213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        LinearLayoutManager layout = new LinearLayoutManager(getContext(),
305213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                LinearLayoutManager.VERTICAL, false);
306213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.setLayoutManager(layout);
307213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
308213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        // 100x100 pixel views
309213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
3108a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            @NonNull
311213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
3128a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
3138a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
314213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                View view = new View(getContext());
315213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                view.setMinimumWidth(100);
316213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                view.setMinimumHeight(100);
317213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                assertTrue(mRecyclerView.isComputingLayout());
318213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                return new RecyclerView.ViewHolder(view) {};
319213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            }
320213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
321213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
3228a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {}
323213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
324213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            @Override
325213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            public int getItemCount() {
326213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik                return 100;
327213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik            }
328213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        });
329213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
330213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        layout(100, 100);
331213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
332213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        layout.setOrientation(LinearLayoutManager.HORIZONTAL);
333213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
334213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        // Prefetch an item after changing orientation, before layout - shouldn't crash
335213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(1, 1);
336213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
337213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    }
338213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik
339213c0aa34bc54fb1b540a040609f097a4e4f65faChris Craik    @Test
3400951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    public void prefetchDrag() {
3410951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // event dispatch requires a parent
3420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        ViewGroup parent = new FrameLayout(getContext());
3430951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        parent.addView(mRecyclerView);
3440951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3460951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.setLayoutManager(
3470951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
3480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3490951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // 1000x1000 pixel views
3500951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
3510951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3528a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
3538a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
3540951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                mRecyclerView.registerTimePassingMs(5);
3550951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                View view = new View(getContext());
3560951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                view.setMinimumWidth(1000);
3570951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                view.setMinimumHeight(1000);
3580951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                return new RecyclerView.ViewHolder(view) {};
3590951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3600951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3610951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3628a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
3630951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                mRecyclerView.registerTimePassingMs(5);
3640951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3650951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3660951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3670951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            public int getItemCount() {
3680951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                return 100;
3690951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3700951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        };
3710951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.setAdapter(adapter);
3720951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3730951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        layout(1000, 1000);
3740951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3750951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        long time = SystemClock.uptimeMillis();
3760951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3770951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 500, 1000, 0));
3780951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3790951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3800951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3810951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3820951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // Consume slop
3830951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3840951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 500, 0));
3850951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3860951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 0,30
3870951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3880951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 470, 0));
3890951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3900951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(30, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3910951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3920951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 10,15
3930951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3940951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0));
3950951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3960951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3970951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3980951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 0,0 - IGNORED
3990951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
4000951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0));
4010951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); // same as prev
4020951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); // same as prev
4030951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    }
4040951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
4050951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    @Test
406a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    public void prefetchItemsRespectDeadline() {
407a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
408a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
409a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // 100x100 pixel views
410a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
411a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
4128a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
4138a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
414a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
415a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                View view = new View(getContext());
416a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumWidth(100);
417a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumHeight(100);
418945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return new RecyclerView.ViewHolder(view) {};
419a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
420a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
421a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
4228a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(
4238a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull RecyclerView.ViewHolder holder, int position) {
424a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
425a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
426a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
427a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
428a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public int getItemCount() {
429a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                return 100;
430a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
431a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        };
432a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setAdapter(adapter);
433a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
434dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 300);
435a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
436356880d3de117b067522ad452f4e3eed85ce444cChris Craik        // offset scroll so that no prefetch-able views are directly adjacent to viewport
437356880d3de117b067522ad452f4e3eed85ce444cChris Craik        mRecyclerView.scrollBy(0, 50);
438356880d3de117b067522ad452f4e3eed85ce444cChris Craik
439a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
440a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
441a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
442a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe
443a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19);
444a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
445a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Timed prefetch
446945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
44747d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
448a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
449a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // will have enough time to inflate/bind one view, and inflate another
450a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 1);
451a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
452356880d3de117b067522ad452f4e3eed85ce444cChris Craik        // Note: order/view below is an implementation detail
453356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12);
454a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
455a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
456a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Unbounded prefetch this time
45747d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
458a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
459a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should finish all work
460a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
461a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
462356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12, 13, 14);
463945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    }
464945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
465945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    @Test
4661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    public void partialPrefetchAvoidsViewRecycledCallback() {
4671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
4681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // 100x100 pixel views
4701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
4711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4728a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
4738a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
4741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView.registerTimePassingMs(5);
4751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                View view = new View(getContext());
4761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                view.setMinimumWidth(100);
4771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                view.setMinimumHeight(100);
4781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return new RecyclerView.ViewHolder(view) {};
4791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4828a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
4831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView.registerTimePassingMs(5);
4841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public int getItemCount() {
4881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return 100;
4891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4928a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
4931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                // verify unbound view doesn't get
4941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                assertNotEquals(RecyclerView.NO_POSITION, holder.getAdapterPosition());
4951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        };
4971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(adapter);
4981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4991e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(100, 300);
5001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // offset scroll so that no prefetch-able views are directly adjacent to viewport
5021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.scrollBy(0, 50);
5031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5041e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
5051e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
5061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5071e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // Should take 10 ms to inflate + bind, so just give it 9 so it doesn't have time to bind
5081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9);
5091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // Timed prefetch
5111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
5121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
5131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // will have enough time to inflate but not bind one view
5151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
5161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
5171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.ViewHolder pooledHolder = mRecyclerView.getRecycledViewPool()
5181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .mScrap.get(0).mScrapHeap.get(0);
5191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAdapterPosition());
5201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
5211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
5221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
523945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    public void prefetchStaggeredItemsPriority() {
524945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        StaggeredGridLayoutManager sglm =
525945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
526945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.setLayoutManager(sglm);
527945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
528945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // first view 50x100 pixels, rest are 100x100 so second column is offset
529945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
5308a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            @NonNull
531945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
5328a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
5338a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
534945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return new RecyclerView.ViewHolder(new View(getContext())) {};
535945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
536945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
537945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
5388a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
539945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                holder.itemView.setMinimumWidth(100);
540945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                holder.itemView.setMinimumHeight(position == 0 ? 50 : 100);
541945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
542945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
543945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
544945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            public int getItemCount() {
545945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return 100;
546945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
547945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        });
548945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
549dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 200);
550945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
551945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        /* Each row is 50 pixels:
552945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         * ------------- *
553945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   0   |   1   *
554945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   1   *
555945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   3   *
556945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___4___|___3___*
557945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   5   *
558945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   6   |   5   *
559945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *      ...      *
560945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         */
561945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.getChildCount());
562945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(0, sglm.getFirstChildPosition());
563945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(4, sglm.getLastChildPosition());
564945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
565945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching down shows 5 at 0 pixels away, 6 at 50 pixels away
566356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10,
567945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {5, 0}, new Integer[] {6, 50});
568945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
569945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // Prefetch upward shows nothing
570356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
571945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
572945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.scrollBy(0, 100);
573945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
574945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        /* Each row is 50 pixels:
575945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         * ------------- *
576945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   0   |   1   *
577945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___2___|___1___*
578945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   3   *
579945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   3   *
580945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   5   *
581945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___6___|___5___*
582945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   6   |   7   *
583945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   8   |   7   *
584945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *      ...      *
585945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         */
586945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
587945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.getChildCount());
588945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(2, sglm.getFirstChildPosition());
589945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(6, sglm.getLastChildPosition());
590945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
591945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching down shows 7 at 0 pixels away, 8 at 50 pixels away
592356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10,
593945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {7, 0}, new Integer[] {8, 50});
594945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
595945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching up shows 1 is 0 pixels away, 0 at 50 pixels away
596356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10,
597945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {1, 0}, new Integer[] {0, 50});
598945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    }
599dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
600dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    @Test
601695d9b8629fa048795298359d4845f0747364fafChris Craik    public void prefetchStaggeredPastBoundary() {
602695d9b8629fa048795298359d4845f0747364fafChris Craik        StaggeredGridLayoutManager sglm =
603695d9b8629fa048795298359d4845f0747364fafChris Craik                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
604695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.setLayoutManager(sglm);
605695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
6068a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            @NonNull
607695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
6088a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
6098a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
610695d9b8629fa048795298359d4845f0747364fafChris Craik                return new RecyclerView.ViewHolder(new View(getContext())) {};
611695d9b8629fa048795298359d4845f0747364fafChris Craik            }
612695d9b8629fa048795298359d4845f0747364fafChris Craik
613695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
6148a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
615695d9b8629fa048795298359d4845f0747364fafChris Craik                holder.itemView.setMinimumWidth(100);
616695d9b8629fa048795298359d4845f0747364fafChris Craik                holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
617695d9b8629fa048795298359d4845f0747364fafChris Craik            }
618695d9b8629fa048795298359d4845f0747364fafChris Craik
619695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
620695d9b8629fa048795298359d4845f0747364fafChris Craik            public int getItemCount() {
621695d9b8629fa048795298359d4845f0747364fafChris Craik                return 2;
622695d9b8629fa048795298359d4845f0747364fafChris Craik            }
623695d9b8629fa048795298359d4845f0747364fafChris Craik        });
624695d9b8629fa048795298359d4845f0747364fafChris Craik
625695d9b8629fa048795298359d4845f0747364fafChris Craik        layout(200, 100);
626695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.scrollBy(0, 50);
627695d9b8629fa048795298359d4845f0747364fafChris Craik
628695d9b8629fa048795298359d4845f0747364fafChris Craik        /* Each row is 50 pixels:
629695d9b8629fa048795298359d4845f0747364fafChris Craik         * ------------- *
630695d9b8629fa048795298359d4845f0747364fafChris Craik         *___0___|___1___*
631695d9b8629fa048795298359d4845f0747364fafChris Craik         *   0   |   1   *
632695d9b8629fa048795298359d4845f0747364fafChris Craik         *_______|___1___*
633695d9b8629fa048795298359d4845f0747364fafChris Craik         *       |   1   *
634695d9b8629fa048795298359d4845f0747364fafChris Craik         */
635695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(2, mRecyclerView.getChildCount());
636695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(0, sglm.getFirstChildPosition());
637695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(1, sglm.getLastChildPosition());
638695d9b8629fa048795298359d4845f0747364fafChris Craik
639695d9b8629fa048795298359d4845f0747364fafChris Craik        // prefetch upward gets nothing
640695d9b8629fa048795298359d4845f0747364fafChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
641695d9b8629fa048795298359d4845f0747364fafChris Craik
642695d9b8629fa048795298359d4845f0747364fafChris Craik        // prefetch downward gets nothing (and doesn't crash...)
643695d9b8629fa048795298359d4845f0747364fafChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
644695d9b8629fa048795298359d4845f0747364fafChris Craik    }
645695d9b8629fa048795298359d4845f0747364fafChris Craik
646695d9b8629fa048795298359d4845f0747364fafChris Craik    @Test
647dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    public void prefetchItemsSkipAnimations() {
648dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
649dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setLayoutManager(llm);
650dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        final int[] expandedPosition = new int[] {-1};
651dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
652dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        final RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
653dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
6548a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(
6558a11e6829c522aa1efcc903afa4c01d337082eabChris Craik                    @NonNull ViewGroup parent, int viewType) {
6561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return new RecyclerView.ViewHolder(new View(parent.getContext())) {};
657dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
658dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
659dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
6608a11e6829c522aa1efcc903afa4c01d337082eabChris Craik            public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
661dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                int height = expandedPosition[0] == position ? 400 : 100;
662dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(200, height));
663dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
664dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
665dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
666dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            public int getItemCount() {
667dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                return 10;
668dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
669dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        };
670dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
671dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // make move duration long enough to be able to see the effects
672dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
673dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        itemAnimator.setMoveDuration(10000);
674dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setAdapter(adapter);
675dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
676dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
677dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
678dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        expandedPosition[0] = 1;
679dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // insert payload to avoid creating a new view
680dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        adapter.notifyItemChanged(1, new Object());
681dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
682dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
683dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
684dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
685dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(itemAnimator.isRunning());
686dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(2, llm.getChildCount());
687dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(4, mRecyclerView.getChildCount());
688dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
689dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // animating view should be observable as hidden, uncached...
690dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2);
691dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertNotNull("Animating view should be found, hidden",
692dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                mRecyclerView.mChildHelper.findHiddenNonRemovedView(2));
693dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2));
694dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
695dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // ...but must not be removed for prefetch
696dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
697dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
698dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
699dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals("Prefetch must target one view", 1, mRecyclerView.mPrefetchRegistry.mCount);
700dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        int prefetchTarget = mRecyclerView.mPrefetchRegistry.mPrefetchArray[0];
701dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals("Prefetch must target view 2", 2, prefetchTarget);
702dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
703dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // animating view still observable as hidden, uncached
704dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2);
705dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertNotNull("Animating view should be found, hidden",
706dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                mRecyclerView.mChildHelper.findHiddenNonRemovedView(2));
707dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2));
708dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
709dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(itemAnimator.isRunning());
710dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(2, llm.getChildCount());
711dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(4, mRecyclerView.getChildCount());
712dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    }
7131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
7151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    public void viewHolderFindsNestedRecyclerViews() {
7161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
7171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(llm);
7181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
7201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
7211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
7221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
7231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
7241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                            throws Throwable {
7251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        View view = new RecyclerView(getContext());
7261e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        view.setLayoutParams(new RecyclerView.LayoutParams(100, 100));
7271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        return new RecyclerView.ViewHolder(view) {};
7281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    }
7291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                });
7301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
7311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(mockAdapter);
7321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(100, 200);
7341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        verify(mockAdapter, times(2)).onCreateViewHolder(any(ViewGroup.class), anyInt());
7361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        verify(mockAdapter, times(2)).onBindViewHolder(
737963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas                argThat(new ArgumentMatcher<RecyclerView.ViewHolder>() {
7381e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
739963facb8fce35022f296c38fadafd9a959ab1655Aurimas Liutikas                    public boolean matches(RecyclerView.ViewHolder holder) {
7408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik                        return holder.itemView == holder.mNestedRecyclerView.get();
7411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    }
7421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                }),
7431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                anyInt(),
7441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                any(List.class));
7451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
7461e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7470fab45107f043a4856aad2e1fd785b396898616eChris Craik    class InnerAdapter extends RecyclerView.Adapter<InnerAdapter.ViewHolder> {
7480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        private static final int INNER_ITEM_COUNT = 20;
749f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        int mItemsBound = 0;
750f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
7510fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
7521e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            ViewHolder(View itemView) {
7531e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                super(itemView);
7541e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        InnerAdapter() {}
7581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7608a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
7610fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
7621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            View view = new View(parent.getContext());
7631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            view.setLayoutParams(new RecyclerView.LayoutParams(100, 100));
7641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return new ViewHolder(view);
7651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7688a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
7690fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
770f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik            mItemsBound++;
771f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        }
7721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public int getItemCount() {
7751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return INNER_ITEM_COUNT;
7761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
7781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7790fab45107f043a4856aad2e1fd785b396898616eChris Craik    class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
7801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7810ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        private boolean mReverseInner;
7820ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
7830fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
7841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            private final RecyclerView mRecyclerView;
7851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            ViewHolder(RecyclerView itemView) {
7861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                super(itemView);
7871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView = itemView;
7881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        ArrayList<InnerAdapter> mAdapters = new ArrayList<>();
7920ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        ArrayList<Parcelable> mSavedStates = new ArrayList<>();
7931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
7941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        OuterAdapter() {
7960ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            this(false);
7970ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        }
7980ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
7990ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        OuterAdapter(boolean reverseInner) {
8008123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            this(reverseInner, 10);
8018123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
8028123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
8038123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        OuterAdapter(boolean reverseInner, int itemCount) {
8040ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            mReverseInner = reverseInner;
8058123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            for (int i = 0; i < itemCount; i++) {
8061e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mAdapters.add(new InnerAdapter());
8070ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                mSavedStates.add(null);
8081e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
8091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8118123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        void addItem() {
8128123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            int index = getItemCount();
8138123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            mAdapters.add(new InnerAdapter());
8148123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            mSavedStates.add(null);
8158123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            notifyItemInserted(index);
8168123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
8178123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
8181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8198a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
8200fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
8210fab45107f043a4856aad2e1fd785b396898616eChris Craik
822bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            RecyclerView rv = new RecyclerView(parent.getContext()) {
823bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                @Override
824bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                public int getWindowVisibility() {
825bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                    // Pretend to be visible to avoid being filtered out
826bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                    return View.VISIBLE;
827bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                }
828bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            };
8291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
8300ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                    LinearLayoutManager.HORIZONTAL, mReverseInner));
8311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setRecycledViewPool(mSharedPool);
8321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100));
8331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return new ViewHolder(rv);
8341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8378a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
8380fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
8390fab45107f043a4856aad2e1fd785b396898616eChris Craik
8400ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            // Tests may rely on bound holders not being shared between inner adapters,
8410ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            // since we force recycle here
8420ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            holder.mRecyclerView.swapAdapter(mAdapters.get(position), true);
8430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            Parcelable savedState = mSavedStates.get(position);
8450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            if (savedState != null) {
8460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
8470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                mSavedStates.set(position, null);
8480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            }
8490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        }
8500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        @Override
8528a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onViewRecycled(@NonNull ViewHolder holder) {
8530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            mSavedStates.set(holder.getAdapterPosition(),
8540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                    holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
8551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public int getItemCount() {
8598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            return mAdapters.size();
8601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
8621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
8640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchSimple() {
8651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
866d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik        assertEquals(2, llm.getInitialPrefetchItemCount());
8671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(llm);
8691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(new OuterAdapter());
8701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(200, 200);
8721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
8731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // prefetch 2 (default)
8751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
8771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertNotNull(holder);
8781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertNotNull(holder.mNestedRecyclerView);
8798123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        RecyclerView innerView = holder.mNestedRecyclerView.get();
8808123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
8811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // prefetch 4
8838123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        ((LinearLayoutManager) innerView.getLayoutManager())
8841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .setInitialPrefetchItemCount(4);
8851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8868123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3);
8871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
8880ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8890ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    @Test
8900fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    public void nestedPrefetchNotClearInnerStructureChangeFlag() {
8910fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        LinearLayoutManager llm = new LinearLayoutManager(getContext());
8920fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertEquals(2, llm.getInitialPrefetchItemCount());
8930fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8940fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.setLayoutManager(llm);
8950fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.setAdapter(new OuterAdapter());
8960fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8970fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
8980fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
8990fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9000fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch 2 (default)
9010fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9020fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9030fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertNotNull(holder);
9040fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertNotNull(holder.mNestedRecyclerView);
9050fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView innerView = holder.mNestedRecyclerView.get();
9060fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView.Adapter innerAdapter = innerView.getAdapter();
9070fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
9080fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // mStructureChanged is initially true before first layout pass.
9090fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
9100fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
9110fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9120fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // layout position 2 and clear mStructureChanged
9130fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.scrollToPosition(2);
9140fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
9150fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.scrollToPosition(0);
9160fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
9170fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertFalse(innerView.mState.mStructureChanged);
9180fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertFalse(innerView.hasPendingAdapterUpdates());
9190fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9200fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // notify change on the cached innerView.
9210fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        innerAdapter.notifyDataSetChanged();
9220fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
9230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
9240fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9250fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch again
9260fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
9270fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        ((LinearLayoutManager) innerView.getLayoutManager())
9280fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu                .setInitialPrefetchItemCount(2);
9290fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9300fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
9310fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9320fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // The re-prefetch is not necessary get the same inner view but we will get same Adapter
9330fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9340fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        innerView = holder.mNestedRecyclerView.get();
9350fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertSame(innerAdapter, innerView.getAdapter());
9360fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch shouldn't clear the mStructureChanged flag
9370fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
9380fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
9390fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    }
9400fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
9410fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    @Test
9420ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchReverseInner() {
9430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setAdapter(new OuterAdapter(/* reverseInner = */ true));
9450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        layout(200, 200);
9470ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
9480ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9500ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9510ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9520ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // anchor from right side, should see last two positions
9538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19);
9540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    }
9550ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9560ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    @Test
9570ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchOffset() {
9580ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9590ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setAdapter(new OuterAdapter());
9600ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9610ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        layout(200, 200);
9620ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9630ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // Scroll top row by 5.5 items, verify positions 5, 6, 7 showing
9640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        RecyclerView inner = (RecyclerView) mRecyclerView.getChildAt(0);
9650ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        inner.scrollBy(550, 0);
9660ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(5, RecyclerView.getChildViewHolderInt(inner.getChildAt(0)).mPosition);
9670ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(6, RecyclerView.getChildViewHolderInt(inner.getChildAt(1)).mPosition);
9680ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(7, RecyclerView.getChildViewHolderInt(inner.getChildAt(2)).mPosition);
9690ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9700ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // scroll down 4 rows, up 3 so row 0 is adjacent but uncached
9710ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.scrollBy(0, 400);
9720ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.scrollBy(0, -300);
9730ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9740ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // top row no longer present
9750ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 0);
9760ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9770ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // prefetch upward, and validate that we've gotten the top row with correct offsets
9780ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
9790ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9800ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        inner = (RecyclerView) CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0).itemView;
9810ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6);
9820ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9830ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // prefetch 4
9840ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        ((LinearLayoutManager) inner.getLayoutManager()).setInitialPrefetchItemCount(4);
9850ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9860ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6, 7, 8);
9870ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    }
988f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
989f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    @Test
990f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    public void nestedPrefetchNotReset() {
991f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
992f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        OuterAdapter outerAdapter = new OuterAdapter();
993f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.setAdapter(outerAdapter);
994f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
995f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        layout(200, 200);
996f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
997f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
998f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
999f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // prefetch row 2, items 0 & 1
1000f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound);
1001f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1002f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
10038123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
1004f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
1005f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertNotNull(innerRecyclerView);
1006f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1);
1007f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound);
1008f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
1009f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // new row comes on, triggers layout...
1010f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.scrollBy(0, 50);
1011f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
1012f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // ... which shouldn't require new items to be bound,
1013f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // as prefetch has already done that work
1014f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound);
1015f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    }
10160fab45107f043a4856aad2e1fd785b396898616eChris Craik
10170fab45107f043a4856aad2e1fd785b396898616eChris Craik    static void validateRvChildrenValid(RecyclerView recyclerView, int childCount) {
10180fab45107f043a4856aad2e1fd785b396898616eChris Craik        ChildHelper childHelper = recyclerView.mChildHelper;
10190fab45107f043a4856aad2e1fd785b396898616eChris Craik
10200fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(childCount, childHelper.getUnfilteredChildCount());
10210fab45107f043a4856aad2e1fd785b396898616eChris Craik        for (int i = 0; i < childHelper.getUnfilteredChildCount(); i++) {
10220fab45107f043a4856aad2e1fd785b396898616eChris Craik            assertFalse(recyclerView.getChildViewHolder(
10230fab45107f043a4856aad2e1fd785b396898616eChris Craik                    childHelper.getUnfilteredChildAt(i)).isInvalid());
10240fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
10250fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
10260fab45107f043a4856aad2e1fd785b396898616eChris Craik
10270fab45107f043a4856aad2e1fd785b396898616eChris Craik    @Test
10280fab45107f043a4856aad2e1fd785b396898616eChris Craik    public void nestedPrefetchCacheNotTouched() {
10290fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10300fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterAdapter outerAdapter = new OuterAdapter();
10310fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setAdapter(outerAdapter);
10320fab45107f043a4856aad2e1fd785b396898616eChris Craik
10330fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 200);
10340fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.scrollBy(0, 100);
10350fab45107f043a4856aad2e1fd785b396898616eChris Craik
10360fab45107f043a4856aad2e1fd785b396898616eChris Craik        // item 0 is cached
10370fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
10380fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0);
10398123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
10400fab45107f043a4856aad2e1fd785b396898616eChris Craik
10410fab45107f043a4856aad2e1fd785b396898616eChris Craik        // try and prefetch it
10420fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
10430fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
10440fab45107f043a4856aad2e1fd785b396898616eChris Craik
10450fab45107f043a4856aad2e1fd785b396898616eChris Craik        // make sure cache's inner items aren't rebound unnecessarily
10460fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
10478123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
10488123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    }
10498123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10508123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    @Test
10518123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    public void nestedRemoveAnimatingView() {
10528123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10538123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        OuterAdapter outerAdapter = new OuterAdapter(false, 1);
10548123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.setAdapter(outerAdapter);
10558123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30));
10568123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        layout(200, 200);
10588123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // Insert 3 items - only first one in viewport, so only it animates
10608123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        for (int i = 0; i < 3; i++) {
10618123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            outerAdapter.addItem();
10628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
10638123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        layout(200, 200); // layout again to kick off animation
10648123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10658123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10668123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // item 1 is animating, so scroll it out of viewport
10678123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.scrollBy(0, 200);
10688123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10698123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for...
10708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
10718123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(1, mRecycler.mCachedViews.size());
10728123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0);
10738123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
10748123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10758123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // until animation ends
10768123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.getItemAnimator().endAnimations();
10778123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
10788123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecycler.mCachedViews.size());
10798123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1);
10808123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
10818123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10828123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) {
10838123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            assertNotNull(viewHolder.mNestedRecyclerView);
10848123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
10850fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
10860fab45107f043a4856aad2e1fd785b396898616eChris Craik
10879ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    @Test
10889ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    public void nestedExpandCacheCorrectly() {
10899ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE;
10909ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10919ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10929ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        OuterAdapter outerAdapter = new OuterAdapter();
10939ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.setAdapter(outerAdapter);
10949ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10959ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        layout(200, 200);
10969ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10979ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
10989ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
10999ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11009ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        // after initial prefetch, view cache max expanded by number of inner items prefetched (2)
11019ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
11029ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        RecyclerView innerView = holder.mNestedRecyclerView.get();
11039ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
11049ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved);
11059ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
11069ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11079ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        try {
11089ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // Note: As a hack, we not only must manually dispatch attachToWindow(), but we
11099ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker
11109ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // is registered to the wrong thread, since @setup is called on a different thread
11119ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // from @Test. Assert this, so this test can be fixed when setup == test thread.
11129ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size());
11139ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.isAttachedToWindow());
11149ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.onAttachedToWindow();
11159ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11169ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // bring prefetch view into viewport, at which point it shouldn't have cache expanded...
11179ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            mRecyclerView.scrollBy(0, 100);
11189ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
11199ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved);
11209ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
11219ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11229ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // until a valid horizontal prefetch caches an item, and expands view count by one
11239ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.mPrefetchRegistry.setPrefetchVector(1, 0);
11249ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker
11259ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
11269ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved);
11279ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
11289ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        } finally {
11299ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            if (innerView.isAttachedToWindow()) {
11309ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik                innerView.onDetachedFromWindow();
11319ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            }
11329ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        }
11339ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    }
11349ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
11350fab45107f043a4856aad2e1fd785b396898616eChris Craik    /**
11360fab45107f043a4856aad2e1fd785b396898616eChris Craik     * Similar to OuterAdapter above, but uses notifyDataSetChanged() instead of set/swapAdapter
11370fab45107f043a4856aad2e1fd785b396898616eChris Craik     * to update data for the inner RecyclerViews when containing ViewHolder is bound.
11380fab45107f043a4856aad2e1fd785b396898616eChris Craik     */
11390fab45107f043a4856aad2e1fd785b396898616eChris Craik    class OuterNotifyAdapter extends RecyclerView.Adapter<OuterNotifyAdapter.ViewHolder> {
11400fab45107f043a4856aad2e1fd785b396898616eChris Craik        private static final int OUTER_ITEM_COUNT = 10;
11410fab45107f043a4856aad2e1fd785b396898616eChris Craik
11420fab45107f043a4856aad2e1fd785b396898616eChris Craik        private boolean mReverseInner;
11430fab45107f043a4856aad2e1fd785b396898616eChris Craik
11440fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
11450fab45107f043a4856aad2e1fd785b396898616eChris Craik            private final RecyclerView mRecyclerView;
11460fab45107f043a4856aad2e1fd785b396898616eChris Craik            private final InnerAdapter mAdapter;
11470fab45107f043a4856aad2e1fd785b396898616eChris Craik            ViewHolder(RecyclerView itemView) {
11480fab45107f043a4856aad2e1fd785b396898616eChris Craik                super(itemView);
11490fab45107f043a4856aad2e1fd785b396898616eChris Craik                mRecyclerView = itemView;
11500fab45107f043a4856aad2e1fd785b396898616eChris Craik                mAdapter = new InnerAdapter();
11510fab45107f043a4856aad2e1fd785b396898616eChris Craik                mRecyclerView.setAdapter(mAdapter);
11520fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11530fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11540fab45107f043a4856aad2e1fd785b396898616eChris Craik
11550fab45107f043a4856aad2e1fd785b396898616eChris Craik        ArrayList<Parcelable> mSavedStates = new ArrayList<>();
11560fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
11570fab45107f043a4856aad2e1fd785b396898616eChris Craik
11580fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter() {
11590fab45107f043a4856aad2e1fd785b396898616eChris Craik            this(false);
11600fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11610fab45107f043a4856aad2e1fd785b396898616eChris Craik
11620fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter(boolean reverseInner) {
11630fab45107f043a4856aad2e1fd785b396898616eChris Craik            mReverseInner = reverseInner;
11640fab45107f043a4856aad2e1fd785b396898616eChris Craik            for (int i = 0; i <= OUTER_ITEM_COUNT; i++) {
11650fab45107f043a4856aad2e1fd785b396898616eChris Craik                mSavedStates.add(null);
11660fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11670fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11680fab45107f043a4856aad2e1fd785b396898616eChris Craik
11698a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        @NonNull
11700fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11718a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
11720fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
11730fab45107f043a4856aad2e1fd785b396898616eChris Craik            RecyclerView rv = new RecyclerView(parent.getContext());
11740fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
11750fab45107f043a4856aad2e1fd785b396898616eChris Craik                    LinearLayoutManager.HORIZONTAL, mReverseInner));
11760fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setRecycledViewPool(mSharedPool);
11770fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100));
11780fab45107f043a4856aad2e1fd785b396898616eChris Craik            return new ViewHolder(rv);
11790fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11800fab45107f043a4856aad2e1fd785b396898616eChris Craik
11810fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11828a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
11830fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
11840fab45107f043a4856aad2e1fd785b396898616eChris Craik            // if we had actual data to put into our adapter, this is where we'd do it...
11850fab45107f043a4856aad2e1fd785b396898616eChris Craik
11860fab45107f043a4856aad2e1fd785b396898616eChris Craik            // ... then notify the adapter that it has new content:
11870fab45107f043a4856aad2e1fd785b396898616eChris Craik            holder.mAdapter.notifyDataSetChanged();
11880fab45107f043a4856aad2e1fd785b396898616eChris Craik
11890fab45107f043a4856aad2e1fd785b396898616eChris Craik            Parcelable savedState = mSavedStates.get(position);
11900fab45107f043a4856aad2e1fd785b396898616eChris Craik            if (savedState != null) {
11910fab45107f043a4856aad2e1fd785b396898616eChris Craik                holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
11920fab45107f043a4856aad2e1fd785b396898616eChris Craik                mSavedStates.set(position, null);
11930fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11940fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11950fab45107f043a4856aad2e1fd785b396898616eChris Craik
11960fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11978a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onViewRecycled(@NonNull ViewHolder holder) {
1198570ab881350c836743247140a0953f87acc21c8dChris Craik            if (holder.getAdapterPosition() >= 0) {
1199570ab881350c836743247140a0953f87acc21c8dChris Craik                mSavedStates.set(holder.getAdapterPosition(),
1200570ab881350c836743247140a0953f87acc21c8dChris Craik                        holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
1201570ab881350c836743247140a0953f87acc21c8dChris Craik            }
12020fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
12030fab45107f043a4856aad2e1fd785b396898616eChris Craik
12040fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
12050fab45107f043a4856aad2e1fd785b396898616eChris Craik        public int getItemCount() {
12060fab45107f043a4856aad2e1fd785b396898616eChris Craik            return OUTER_ITEM_COUNT;
12070fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
12080fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
12090fab45107f043a4856aad2e1fd785b396898616eChris Craik
12100fab45107f043a4856aad2e1fd785b396898616eChris Craik    @Test
12110fab45107f043a4856aad2e1fd785b396898616eChris Craik    public void nestedPrefetchDiscardStaleChildren() {
12120fab45107f043a4856aad2e1fd785b396898616eChris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
1213d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik        assertEquals(2, llm.getInitialPrefetchItemCount());
12140fab45107f043a4856aad2e1fd785b396898616eChris Craik
12150fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setLayoutManager(llm);
12160fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter();
12170fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setAdapter(outerAdapter);
12180fab45107f043a4856aad2e1fd785b396898616eChris Craik
12190fab45107f043a4856aad2e1fd785b396898616eChris Craik        // zero cache, so item we prefetch can't already be ready
12200fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setItemViewCacheSize(0);
12210fab45107f043a4856aad2e1fd785b396898616eChris Craik
12220fab45107f043a4856aad2e1fd785b396898616eChris Craik        // layout 3 items, then resize to 2...
12230fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 300);
12240fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 200);
12250fab45107f043a4856aad2e1fd785b396898616eChris Craik
12260fab45107f043a4856aad2e1fd785b396898616eChris Craik        // so 1 item is evicted into the RecycledViewPool (bypassing cache)
12270fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(1, mRecycler.mRecyclerPool.getRecycledViewCount(0));
12280fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(0, mRecycler.mCachedViews.size());
12290fab45107f043a4856aad2e1fd785b396898616eChris Craik
12300fab45107f043a4856aad2e1fd785b396898616eChris Craik        // This is a simple imitation of other behavior (namely, varied types in the outer adapter)
12310fab45107f043a4856aad2e1fd785b396898616eChris Craik        // that results in the same initial state to test: items in the pool with attached children
12320fab45107f043a4856aad2e1fd785b396898616eChris Craik        for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) {
12330fab45107f043a4856aad2e1fd785b396898616eChris Craik            // verify that children are attached and valid, since the RVs haven't been rebound
12340fab45107f043a4856aad2e1fd785b396898616eChris Craik            assertNotNull(holder.mNestedRecyclerView);
12358123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout);
12368123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
12370fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
12380fab45107f043a4856aad2e1fd785b396898616eChris Craik
12390fab45107f043a4856aad2e1fd785b396898616eChris Craik        // prefetch the outer item bind, but without enough time to do any inner binds
12400fab45107f043a4856aad2e1fd785b396898616eChris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9);
12410fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
12420fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
12430fab45107f043a4856aad2e1fd785b396898616eChris Craik
12440fab45107f043a4856aad2e1fd785b396898616eChris Craik        // 2 is prefetched without children
12450fab45107f043a4856aad2e1fd785b396898616eChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 2);
12460fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
12470fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertNotNull(holder);
12480fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertNotNull(holder.mNestedRecyclerView);
12498123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount());
12508123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size());
12510fab45107f043a4856aad2e1fd785b396898616eChris Craik
12520fab45107f043a4856aad2e1fd785b396898616eChris Craik        // but if we give it more time to bind items, it'll now acquire its inner items
12530fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
12548123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1);
12550fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
1256570ab881350c836743247140a0953f87acc21c8dChris Craik
1257570ab881350c836743247140a0953f87acc21c8dChris Craik
1258570ab881350c836743247140a0953f87acc21c8dChris Craik    @Test
1259570ab881350c836743247140a0953f87acc21c8dChris Craik    public void nestedPrefetchDiscardStalePrefetch() {
1260570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
1261570ab881350c836743247140a0953f87acc21c8dChris Craik        OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter();
1262570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setAdapter(outerAdapter);
1263570ab881350c836743247140a0953f87acc21c8dChris Craik
1264570ab881350c836743247140a0953f87acc21c8dChris Craik        // zero cache, so item we prefetch can't already be ready
1265570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setItemViewCacheSize(0);
1266570ab881350c836743247140a0953f87acc21c8dChris Craik
1267570ab881350c836743247140a0953f87acc21c8dChris Craik        // layout as 2x2, starting on row index 2, with empty cache
1268570ab881350c836743247140a0953f87acc21c8dChris Craik        layout(200, 200);
1269570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.scrollBy(0, 200);
1270570ab881350c836743247140a0953f87acc21c8dChris Craik
1271570ab881350c836743247140a0953f87acc21c8dChris Craik        // no views cached, or previously used (so we can trust number in mItemsBound)
1272570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecycler.mRecyclerPool.clear();
1273570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, mRecycler.mRecyclerPool.getRecycledViewCount(0));
1274570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, mRecycler.mCachedViews.size());
1275570ab881350c836743247140a0953f87acc21c8dChris Craik
1276570ab881350c836743247140a0953f87acc21c8dChris Craik        // prefetch the outer item and its inner children
1277570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1278570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1279570ab881350c836743247140a0953f87acc21c8dChris Craik
1280570ab881350c836743247140a0953f87acc21c8dChris Craik        // 4 is prefetched with 2 inner children, first two binds
1281570ab881350c836743247140a0953f87acc21c8dChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4);
1282570ab881350c836743247140a0953f87acc21c8dChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4);
1283570ab881350c836743247140a0953f87acc21c8dChris Craik        assertNotNull(holder);
1284570ab881350c836743247140a0953f87acc21c8dChris Craik        assertNotNull(holder.mNestedRecyclerView);
1285570ab881350c836743247140a0953f87acc21c8dChris Craik        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
1286570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount());
1287570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size());
1288570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound);
1289570ab881350c836743247140a0953f87acc21c8dChris Craik
1290570ab881350c836743247140a0953f87acc21c8dChris Craik        // notify data set changed, so any previously prefetched items invalid, and re-prefetch
1291570ab881350c836743247140a0953f87acc21c8dChris Craik        innerRecyclerView.getAdapter().notifyDataSetChanged();
1292570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1293570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1294570ab881350c836743247140a0953f87acc21c8dChris Craik
1295570ab881350c836743247140a0953f87acc21c8dChris Craik        // 4 is prefetched again...
1296570ab881350c836743247140a0953f87acc21c8dChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4);
1297570ab881350c836743247140a0953f87acc21c8dChris Craik
1298570ab881350c836743247140a0953f87acc21c8dChris Craik        // reusing the same instance with 2 inner children...
1299570ab881350c836743247140a0953f87acc21c8dChris Craik        assertSame(holder, CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4));
1300570ab881350c836743247140a0953f87acc21c8dChris Craik        assertSame(innerRecyclerView, holder.mNestedRecyclerView.get());
1301570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount());
1302570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size());
1303570ab881350c836743247140a0953f87acc21c8dChris Craik
1304570ab881350c836743247140a0953f87acc21c8dChris Craik        // ... but there should be two new binds
1305570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(4, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound);
1306570ab881350c836743247140a0953f87acc21c8dChris Craik    }
1307945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik}
1308