RecyclerViewCacheTest.java revision b9d3b54b0b5fa223867cf11e47ee3f31ee474031
136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik/*
236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Copyright (C) 2016 The Android Open Source Project
336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik *
436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Licensed under the Apache License, Version 2.0 (the "License");
536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * you may not use this file except in compliance with the License.
636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * You may obtain a copy of the License at
736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik *
836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik *      http://www.apache.org/licenses/LICENSE-2.0
936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik *
1036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * Unless required by applicable law or agreed to in writing, software
1136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * distributed under the License is distributed on an "AS IS" BASIS,
1236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * See the License for the specific language governing permissions and
1436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik * limitations under the License.
1536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik */
1636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
1736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpackage android.support.v7.widget;
1836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
19945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craikimport static org.junit.Assert.assertEquals;
200fab45107f043a4856aad2e1fd785b396898616eChris Craikimport static org.junit.Assert.assertFalse;
211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.junit.Assert.assertNotEquals;
22dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craikimport static org.junit.Assert.assertNotNull;
230fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Guimport static org.junit.Assert.assertSame;
24ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.junit.Assert.assertTrue;
25ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.any;
26ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Matchers.anyInt;
271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport static org.mockito.Matchers.argThat;
28ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.mock;
29ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.never;
30ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.times;
31ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.verify;
32ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport static org.mockito.Mockito.when;
33ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikas
3436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.content.Context;
35e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikasimport android.os.Build;
360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craikimport android.os.Parcelable;
370951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.os.SystemClock;
3836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.InstrumentationRegistry;
39ee9f1a3ebd8f071cea11fd97f9a3f3d41027189cAurimas Liutikasimport android.support.test.filters.SdkSuppress;
40754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.SmallTest;
4136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.support.test.runner.AndroidJUnit4;
420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.view.MotionEvent;
4336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.View;
4436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport android.view.ViewGroup;
450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craikimport android.widget.FrameLayout;
4636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport org.hamcrest.BaseMatcher;
481e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport org.hamcrest.Description;
49356880d3de117b067522ad452f4e3eed85ce444cChris Craikimport org.junit.After;
5036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Before;
5136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.Test;
5236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.junit.runner.RunWith;
5336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.invocation.InvocationOnMock;
5436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport org.mockito.stubbing.Answer;
5536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craikimport java.util.ArrayList;
5736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikimport java.util.List;
58a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craikimport java.util.concurrent.TimeUnit;
5936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
6036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@SmallTest
61e85a5c8c0bfc0aa1ca0796c7bd1f916049e55d68Aurimas Liutikas@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
6236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik@RunWith(AndroidJUnit4.class)
6336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craikpublic class RecyclerViewCacheTest {
64a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    TimeMockingRecyclerView mRecyclerView;
6536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    RecyclerView.Recycler mRecycler;
6636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
67a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    private class TimeMockingRecyclerView extends RecyclerView {
68a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        private long mMockNanoTime = 0;
69a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
70a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        TimeMockingRecyclerView(Context context) {
71a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            super(context);
72a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
73a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
74a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        public void registerTimePassingMs(long ms) {
75a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            mMockNanoTime += TimeUnit.MILLISECONDS.toNanos(ms);
76a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
77a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
78a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        @Override
79a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        long getNanoTime() {
80a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            return mMockNanoTime;
81a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        }
82bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik
83bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        @Override
84bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        public int getWindowVisibility() {
85bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            // Pretend to be visible to avoid being filtered out
86bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            return View.VISIBLE;
87bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik        }
88356880d3de117b067522ad452f4e3eed85ce444cChris Craik    }
89a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
9036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Before
91356880d3de117b067522ad452f4e3eed85ce444cChris Craik    public void setup() throws Exception {
92a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView = new TimeMockingRecyclerView(getContext());
93356880d3de117b067522ad452f4e3eed85ce444cChris Craik        mRecyclerView.onAttachedToWindow();
9436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler = mRecyclerView.mRecycler;
9536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
9636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
97356880d3de117b067522ad452f4e3eed85ce444cChris Craik    @After
98356880d3de117b067522ad452f4e3eed85ce444cChris Craik    public void teardown() throws Exception {
99356880d3de117b067522ad452f4e3eed85ce444cChris Craik        if (mRecyclerView.isAttachedToWindow()) {
100356880d3de117b067522ad452f4e3eed85ce444cChris Craik            mRecyclerView.onDetachedFromWindow();
101356880d3de117b067522ad452f4e3eed85ce444cChris Craik        }
102356880d3de117b067522ad452f4e3eed85ce444cChris Craik        GapWorker gapWorker = GapWorker.sGapWorker.get();
103356880d3de117b067522ad452f4e3eed85ce444cChris Craik        if (gapWorker != null) {
104356880d3de117b067522ad452f4e3eed85ce444cChris Craik            assertTrue(gapWorker.mRecyclerViews.isEmpty());
105356880d3de117b067522ad452f4e3eed85ce444cChris Craik        }
106356880d3de117b067522ad452f4e3eed85ce444cChris Craik    }
107356880d3de117b067522ad452f4e3eed85ce444cChris Craik
10836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    private Context getContext() {
10936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        return InstrumentationRegistry.getContext();
11036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
11136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
112dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    private void layout(int width, int height) {
113dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.measure(
114dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
115dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
116dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.layout(0, 0, width, height);
117dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    }
118dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
11936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
12036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchReusesCacheItems() {
12136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() {
12236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
12336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
12436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
12536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        ViewGroup.LayoutParams.WRAP_CONTENT);
12636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
12736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
12836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
1291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
1303104d446bcf3da9ffb7a761fd30f0506c30f0cc9Chris Craik                    LayoutPrefetchRegistry prefetchManager) {
131945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(0, 0);
132945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(1, 0);
133945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                prefetchManager.addPosition(2, 0);
13436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
13536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
13636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            @Override
13736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
13836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            }
13936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        };
14036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(prefetchingLayoutManager);
14136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
14236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
14336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
14436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
14536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
14636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
14736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
14836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(new View(getContext())) {};
14936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
15036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
15136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(10);
15236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
15336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
154dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(320, 320);
15536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
15636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt());
15736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        verify(mockAdapter, never()).onBindViewHolder(
15836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
15936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
16036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Prefetch multiple times...
16236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        for (int i = 0; i < 4; i++) {
16347d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik            mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
16436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
16536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            // ...but should only see the same three items fetched/bound once each
16636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onCreateViewHolder(any(ViewGroup.class), anyInt());
16736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            verify(mockAdapter, times(3)).onBindViewHolder(
16836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    any(RecyclerView.ViewHolder.class), anyInt(), any(List.class));
16936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            assertTrue(mRecycler.mCachedViews.size() == 3);
1711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 0, 1, 2);
17236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
17336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
17436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
17636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedWithInserts() {
177dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
17836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
17936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
18036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
18136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
18236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
18336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
18436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
185dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        View view = new View(getContext());
186dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        view.setMinimumWidth(100);
187dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        view.setMinimumHeight(100);
188dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                        return new RecyclerView.ViewHolder(view) {};
18936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
19036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
19136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
19236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
19336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
194dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 100);
19536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
196945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(2, mRecyclerView.mRecycler.mViewCacheMax);
197dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
19847d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
199945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.mRecycler.mViewCacheMax);
200945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
2011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 3, 4, 5);
20236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // further views recycled, as though from scrolling, shouldn't evict prefetched views:
20436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(10));
205dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10);
20636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
20736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(20));
208dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 10, 20);
20936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(30));
211dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 20, 30);
21236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecycler.recycleView(mRecycler.getViewForPosition(40));
214dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 3, 4, 5, 30, 40);
21536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
21636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // After clearing the cache, the prefetch priorities should be cleared as well:
21736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.mRecycler.recycleAndClearCachedViews();
218dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        for (int i : new int[] {3, 4, 5, 50, 60, 70, 80, 90}) {
21936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik            mRecycler.recycleView(mRecycler.getViewForPosition(i));
22036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        }
22136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // cache only contains most recent positions, no priority for previous prefetches:
223356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 50, 60, 70, 80, 90);
22436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
22536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
22636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    @Test
22736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    public void prefetchItemsNotEvictedOnScroll() {
22836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
22936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
23036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // 100x100 pixel views
23136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
23236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
23336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
23436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    @Override
23536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
23636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                            throws Throwable {
23736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        View view = new View(getContext());
23836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumWidth(100);
23936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        view.setMinimumHeight(100);
24036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                        return new RecyclerView.ViewHolder(view) {};
24136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                    }
24236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik                });
24336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
24436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setAdapter(mockAdapter);
24536c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
24636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // NOTE: requested cache size must be smaller than span count so two rows cannot fit
24736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.setItemViewCacheSize(2);
24836c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
249dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 150);
25036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 75);
25136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.isEmpty());
25236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
25336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // rows 0, 1, and 2 are all attached and visible. Prefetch row 3:
254945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
25547d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
25636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
25736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is cached:
258356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11);
25936c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
26036c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26136c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // Scroll so 1 falls off (though 3 is still not on screen)
26236c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        mRecyclerView.scrollBy(0, 50);
26336c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik
26436c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        // row 3 is still cached, with a couple other recycled views:
265356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 9, 10, 11);
26636c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik        assertTrue(mRecycler.mCachedViews.size() == 5);
26736c4d66a1bc449309f0a3b97d61dd5bf692d181dChris Craik    }
268a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
269a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    @Test
270b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    public void prefetchIsComputingLayout() {
271b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
272b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
273b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        // 100x100 pixel views
274b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
275b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
276b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
277b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    @Override
278b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
279b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                            throws Throwable {
280b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        View view = new View(getContext());
281b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        view.setMinimumWidth(100);
282b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        view.setMinimumHeight(100);
283b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        assertTrue(mRecyclerView.isComputingLayout());
284b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                        return new RecyclerView.ViewHolder(view) {};
285b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                    }
286b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik                });
287b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
288b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.setAdapter(mockAdapter);
289b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
290b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        layout(100, 100);
291b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
292b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        verify(mockAdapter, times(1)).onCreateViewHolder(mRecyclerView, 0);
293b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
294b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        // prefetch an item, should still observe isComputingLayout in that create
295b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
296b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
297b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
298b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik        verify(mockAdapter, times(2)).onCreateViewHolder(mRecyclerView, 0);
299b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    }
300b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik
301b9d3b54b0b5fa223867cf11e47ee3f31ee474031Chris Craik    @Test
3020951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    public void prefetchDrag() {
3030951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // event dispatch requires a parent
3040951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        ViewGroup parent = new FrameLayout(getContext());
3050951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        parent.addView(mRecyclerView);
3060951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3070951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3080951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.setLayoutManager(
3090951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
3100951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3110951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // 1000x1000 pixel views
3120951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
3130951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3140951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
3150951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                mRecyclerView.registerTimePassingMs(5);
3160951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                View view = new View(getContext());
3170951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                view.setMinimumWidth(1000);
3180951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                view.setMinimumHeight(1000);
3190951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                return new RecyclerView.ViewHolder(view) {};
3200951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3210951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3220951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3230951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
3240951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                mRecyclerView.registerTimePassingMs(5);
3250951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3260951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3270951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            @Override
3280951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            public int getItemCount() {
3290951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                return 100;
3300951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik            }
3310951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        };
3320951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.setAdapter(adapter);
3330951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3340951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        layout(1000, 1000);
3350951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3360951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        long time = SystemClock.uptimeMillis();
3370951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3380951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 500, 1000, 0));
3390951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3400951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3410951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3420951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3430951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // Consume slop
3440951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3450951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 500, 0));
3460951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3470951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 0,30
3480951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3490951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 50, 470, 0));
3500951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(0, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3510951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(30, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3520951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3530951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 10,15
3540951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3550951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0));
3560951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx);
3570951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy);
3580951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3590951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        // move by 0,0 - IGNORED
3600951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        mRecyclerView.onTouchEvent(
3610951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik                MotionEvent.obtain(time, time, MotionEvent.ACTION_MOVE, 40, 455, 0));
3620951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(10, mRecyclerView.mPrefetchRegistry.mPrefetchDx); // same as prev
3630951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik        assertEquals(15, mRecyclerView.mPrefetchRegistry.mPrefetchDy); // same as prev
3640951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    }
3650951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik
3660951d1a9c3200368a7b19bd272f7d6c266a11869Chris Craik    @Test
367a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik    public void prefetchItemsRespectDeadline() {
368a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
369a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
370a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // 100x100 pixel views
371a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
372a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
373a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
374a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
375a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                View view = new View(getContext());
376a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumWidth(100);
377a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                view.setMinimumHeight(100);
378945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return new RecyclerView.ViewHolder(view) {};
379a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
380a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
381a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
382a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
383a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                mRecyclerView.registerTimePassingMs(5);
384a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
385a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
386a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            @Override
387a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            public int getItemCount() {
388a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik                return 100;
389a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik            }
390a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        };
391a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        mRecyclerView.setAdapter(adapter);
392a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
393dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(300, 300);
394a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
395356880d3de117b067522ad452f4e3eed85ce444cChris Craik        // offset scroll so that no prefetch-able views are directly adjacent to viewport
396356880d3de117b067522ad452f4e3eed85ce444cChris Craik        mRecyclerView.scrollBy(0, 50);
397356880d3de117b067522ad452f4e3eed85ce444cChris Craik
398a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
399a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
400a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
401a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should take 15 ms to inflate, bind, inflate, so give 19 to be safe
402a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19);
403a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
404a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Timed prefetch
405945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
40647d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
407a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
408a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // will have enough time to inflate/bind one view, and inflate another
409a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 1);
410a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
411356880d3de117b067522ad452f4e3eed85ce444cChris Craik        // Note: order/view below is an implementation detail
412356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12);
413a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
414a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
415a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Unbounded prefetch this time
41647d17a72daeb141148d13a0b0f2df146f6524a9dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
417a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik
418a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        // Should finish all work
419a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 3);
420a93a0c08b4543401d8e2df9bd4c88f26d3b72c11Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
421356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 12, 13, 14);
422945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    }
423945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
424945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    @Test
4251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    public void partialPrefetchAvoidsViewRecycledCallback() {
4261e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
4271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // 100x100 pixel views
4291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
4301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
4321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView.registerTimePassingMs(5);
4331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                View view = new View(getContext());
4341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                view.setMinimumWidth(100);
4351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                view.setMinimumHeight(100);
4361e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return new RecyclerView.ViewHolder(view) {};
4371e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4381e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4391e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4401e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
4411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView.registerTimePassingMs(5);
4421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public int getItemCount() {
4461e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return 100;
4471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4481e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4491e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            @Override
4501e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            public void onViewRecycled(RecyclerView.ViewHolder holder) {
4511e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                // verify unbound view doesn't get
4521e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                assertNotEquals(RecyclerView.NO_POSITION, holder.getAdapterPosition());
4531e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
4541e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        };
4551e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(adapter);
4561e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4571e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(100, 300);
4581e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4591e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // offset scroll so that no prefetch-able views are directly adjacent to viewport
4601e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.scrollBy(0, 50);
4611e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4621e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
4631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
4641e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // Should take 10 ms to inflate + bind, so just give it 9 so it doesn't have time to bind
4661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9);
4671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // Timed prefetch
4691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
4701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
4711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // will have enough time to inflate but not bind one view
4731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecycler.mCachedViews.size() == 0);
4741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
4751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.ViewHolder pooledHolder = mRecyclerView.getRecycledViewPool()
4761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .mScrap.get(0).mScrapHeap.get(0);
4771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertEquals(RecyclerView.NO_POSITION, pooledHolder.getAdapterPosition());
4781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
4791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
4801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
481945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    public void prefetchStaggeredItemsPriority() {
482945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        StaggeredGridLayoutManager sglm =
483945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
484945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.setLayoutManager(sglm);
485945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
486945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // first view 50x100 pixels, rest are 100x100 so second column is offset
487945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
488945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
489945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
490945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return new RecyclerView.ViewHolder(new View(getContext())) {};
491945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
492945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
493945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
494945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
495945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                holder.itemView.setMinimumWidth(100);
496945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                holder.itemView.setMinimumHeight(position == 0 ? 50 : 100);
497945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
498945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
499945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            @Override
500945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            public int getItemCount() {
501945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                return 100;
502945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik            }
503945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        });
504945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
505dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 200);
506945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
507945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        /* Each row is 50 pixels:
508945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         * ------------- *
509945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   0   |   1   *
510945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   1   *
511945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   3   *
512945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___4___|___3___*
513945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   5   *
514945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   6   |   5   *
515945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *      ...      *
516945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         */
517945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.getChildCount());
518945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(0, sglm.getFirstChildPosition());
519945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(4, sglm.getLastChildPosition());
520945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
521945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching down shows 5 at 0 pixels away, 6 at 50 pixels away
522356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10,
523945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {5, 0}, new Integer[] {6, 50});
524945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
525945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // Prefetch upward shows nothing
526356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
527945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
528945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        mRecyclerView.scrollBy(0, 100);
529945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
530945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        /* Each row is 50 pixels:
531945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         * ------------- *
532945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   0   |   1   *
533945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___2___|___1___*
534945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   2   |   3   *
535945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   3   *
536945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   4   |   5   *
537945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *___6___|___5___*
538945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   6   |   7   *
539945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *   8   |   7   *
540945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         *      ...      *
541945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik         */
542945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
543945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(5, mRecyclerView.getChildCount());
544945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(2, sglm.getFirstChildPosition());
545945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        assertEquals(6, sglm.getLastChildPosition());
546945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
547945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching down shows 7 at 0 pixels away, 8 at 50 pixels away
548356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10,
549945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {7, 0}, new Integer[] {8, 50});
550945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik
551945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik        // prefetching up shows 1 is 0 pixels away, 0 at 50 pixels away
552356880d3de117b067522ad452f4e3eed85ce444cChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10,
553945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik                new Integer[] {1, 0}, new Integer[] {0, 50});
554945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik    }
555dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
556dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    @Test
557695d9b8629fa048795298359d4845f0747364fafChris Craik    public void prefetchStaggeredPastBoundary() {
558695d9b8629fa048795298359d4845f0747364fafChris Craik        StaggeredGridLayoutManager sglm =
559695d9b8629fa048795298359d4845f0747364fafChris Craik                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
560695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.setLayoutManager(sglm);
561695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
562695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
563695d9b8629fa048795298359d4845f0747364fafChris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
564695d9b8629fa048795298359d4845f0747364fafChris Craik                return new RecyclerView.ViewHolder(new View(getContext())) {};
565695d9b8629fa048795298359d4845f0747364fafChris Craik            }
566695d9b8629fa048795298359d4845f0747364fafChris Craik
567695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
568695d9b8629fa048795298359d4845f0747364fafChris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
569695d9b8629fa048795298359d4845f0747364fafChris Craik                holder.itemView.setMinimumWidth(100);
570695d9b8629fa048795298359d4845f0747364fafChris Craik                holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
571695d9b8629fa048795298359d4845f0747364fafChris Craik            }
572695d9b8629fa048795298359d4845f0747364fafChris Craik
573695d9b8629fa048795298359d4845f0747364fafChris Craik            @Override
574695d9b8629fa048795298359d4845f0747364fafChris Craik            public int getItemCount() {
575695d9b8629fa048795298359d4845f0747364fafChris Craik                return 2;
576695d9b8629fa048795298359d4845f0747364fafChris Craik            }
577695d9b8629fa048795298359d4845f0747364fafChris Craik        });
578695d9b8629fa048795298359d4845f0747364fafChris Craik
579695d9b8629fa048795298359d4845f0747364fafChris Craik        layout(200, 100);
580695d9b8629fa048795298359d4845f0747364fafChris Craik        mRecyclerView.scrollBy(0, 50);
581695d9b8629fa048795298359d4845f0747364fafChris Craik
582695d9b8629fa048795298359d4845f0747364fafChris Craik        /* Each row is 50 pixels:
583695d9b8629fa048795298359d4845f0747364fafChris Craik         * ------------- *
584695d9b8629fa048795298359d4845f0747364fafChris Craik         *___0___|___1___*
585695d9b8629fa048795298359d4845f0747364fafChris Craik         *   0   |   1   *
586695d9b8629fa048795298359d4845f0747364fafChris Craik         *_______|___1___*
587695d9b8629fa048795298359d4845f0747364fafChris Craik         *       |   1   *
588695d9b8629fa048795298359d4845f0747364fafChris Craik         */
589695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(2, mRecyclerView.getChildCount());
590695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(0, sglm.getFirstChildPosition());
591695d9b8629fa048795298359d4845f0747364fafChris Craik        assertEquals(1, sglm.getLastChildPosition());
592695d9b8629fa048795298359d4845f0747364fafChris Craik
593695d9b8629fa048795298359d4845f0747364fafChris Craik        // prefetch upward gets nothing
594695d9b8629fa048795298359d4845f0747364fafChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
595695d9b8629fa048795298359d4845f0747364fafChris Craik
596695d9b8629fa048795298359d4845f0747364fafChris Craik        // prefetch downward gets nothing (and doesn't crash...)
597695d9b8629fa048795298359d4845f0747364fafChris Craik        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
598695d9b8629fa048795298359d4845f0747364fafChris Craik    }
599695d9b8629fa048795298359d4845f0747364fafChris Craik
600695d9b8629fa048795298359d4845f0747364fafChris Craik    @Test
601dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    public void prefetchItemsSkipAnimations() {
602dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
603dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setLayoutManager(llm);
604dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        final int[] expandedPosition = new int[] {-1};
605dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
606dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        final RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
607dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
608dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
6091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                return new RecyclerView.ViewHolder(new View(parent.getContext())) {};
610dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
611dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
612dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
613dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
614dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                int height = expandedPosition[0] == position ? 400 : 100;
615dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(200, height));
616dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
617dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
618dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            @Override
619dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            public int getItemCount() {
620dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                return 10;
621dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik            }
622dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        };
623dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
624dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // make move duration long enough to be able to see the effects
625dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
626dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        itemAnimator.setMoveDuration(10000);
627dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.setAdapter(adapter);
628dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
629dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
630dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
631dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        expandedPosition[0] = 1;
632dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // insert payload to avoid creating a new view
633dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        adapter.notifyItemChanged(1, new Object());
634dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
635dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
636dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        layout(200, 400);
637dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
638dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(itemAnimator.isRunning());
639dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(2, llm.getChildCount());
640dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(4, mRecyclerView.getChildCount());
641dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
642dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // animating view should be observable as hidden, uncached...
643dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2);
644dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertNotNull("Animating view should be found, hidden",
645dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                mRecyclerView.mChildHelper.findHiddenNonRemovedView(2));
646dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2));
647dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
648dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // ...but must not be removed for prefetch
649dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
650dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
651dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
652dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals("Prefetch must target one view", 1, mRecyclerView.mPrefetchRegistry.mCount);
653dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        int prefetchTarget = mRecyclerView.mPrefetchRegistry.mPrefetchArray[0];
654dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals("Prefetch must target view 2", 2, prefetchTarget);
655dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
656dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        // animating view still observable as hidden, uncached
657dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 2);
658dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertNotNull("Animating view should be found, hidden",
659dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik                mRecyclerView.mChildHelper.findHiddenNonRemovedView(2));
660dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(GapWorker.isPrefetchPositionAttached(mRecyclerView, 2));
661dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik
662dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertTrue(itemAnimator.isRunning());
663dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(2, llm.getChildCount());
664dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik        assertEquals(4, mRecyclerView.getChildCount());
665dbf84daff4f8c5a9e1b8cfa63ab14106dbab9038Chris Craik    }
6661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
6671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
6681e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    public void viewHolderFindsNestedRecyclerViews() {
6691e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
6701e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(llm);
6711e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
6721e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
6731e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
6741e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .thenAnswer(new Answer<RecyclerView.ViewHolder>() {
6751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
6761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
6771e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                            throws Throwable {
6781e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        View view = new RecyclerView(getContext());
6791e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        view.setLayoutParams(new RecyclerView.LayoutParams(100, 100));
6801e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        return new RecyclerView.ViewHolder(view) {};
6811e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    }
6821e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                });
6831e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        when(mockAdapter.getItemCount()).thenReturn(100);
6841e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(mockAdapter);
6851e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
6861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(100, 200);
6871e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
6881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        verify(mockAdapter, times(2)).onCreateViewHolder(any(ViewGroup.class), anyInt());
6891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        verify(mockAdapter, times(2)).onBindViewHolder(
6901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                argThat(new BaseMatcher<RecyclerView.ViewHolder>() {
6911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
6921e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    public boolean matches(Object item) {
6931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                        RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder) item;
6948123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik                        return holder.itemView == holder.mNestedRecyclerView.get();
6951e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    }
6961e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
6971e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    @Override
6981e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                    public void describeTo(Description description) { }
6991e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                }),
7001e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                anyInt(),
7011e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                any(List.class));
7021e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
7031e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7040fab45107f043a4856aad2e1fd785b396898616eChris Craik    class InnerAdapter extends RecyclerView.Adapter<InnerAdapter.ViewHolder> {
7050ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        private static final int INNER_ITEM_COUNT = 20;
706f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        int mItemsBound = 0;
707f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
7080fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
7091e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            ViewHolder(View itemView) {
7101e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                super(itemView);
7111e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        InnerAdapter() {}
7151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7161e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
7180fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
7191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            View view = new View(parent.getContext());
7201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            view.setLayoutParams(new RecyclerView.LayoutParams(100, 100));
7211e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return new ViewHolder(view);
7221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7231e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
725f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        public void onBindViewHolder(ViewHolder holder, int position) {
7260fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
727f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik            mItemsBound++;
728f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        }
7291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public int getItemCount() {
7321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return INNER_ITEM_COUNT;
7331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
7351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7360fab45107f043a4856aad2e1fd785b396898616eChris Craik    class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
7371e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7380ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        private boolean mReverseInner;
7390ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
7400fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
7411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            private final RecyclerView mRecyclerView;
7421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            ViewHolder(RecyclerView itemView) {
7431e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                super(itemView);
7441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mRecyclerView = itemView;
7451e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7461e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7471e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7481e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        ArrayList<InnerAdapter> mAdapters = new ArrayList<>();
7490ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        ArrayList<Parcelable> mSavedStates = new ArrayList<>();
7501e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
7511e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7521e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        OuterAdapter() {
7530ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            this(false);
7540ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        }
7550ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
7560ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        OuterAdapter(boolean reverseInner) {
7578123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            this(reverseInner, 10);
7588123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
7598123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
7608123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        OuterAdapter(boolean reverseInner, int itemCount) {
7610ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            mReverseInner = reverseInner;
7628123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            for (int i = 0; i < itemCount; i++) {
7631e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                mAdapters.add(new InnerAdapter());
7640ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                mSavedStates.add(null);
7651e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            }
7661e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7671e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7688123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        void addItem() {
7698123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            int index = getItemCount();
7708123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            mAdapters.add(new InnerAdapter());
7718123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            mSavedStates.add(null);
7728123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            notifyItemInserted(index);
7738123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
7748123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
7751e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7761e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
7770fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
7780fab45107f043a4856aad2e1fd785b396898616eChris Craik
779bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            RecyclerView rv = new RecyclerView(parent.getContext()) {
780bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                @Override
781bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                public int getWindowVisibility() {
782bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                    // Pretend to be visible to avoid being filtered out
783bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                    return View.VISIBLE;
784bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik                }
785bec95cedb8511e1d814181aa518512c7e7fc5cf6Chris Craik            };
7861e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
7870ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                    LinearLayoutManager.HORIZONTAL, mReverseInner));
7881e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setRecycledViewPool(mSharedPool);
7891e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100));
7901e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik            return new ViewHolder(rv);
7911e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
7921e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
7931e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
7941e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public void onBindViewHolder(ViewHolder holder, int position) {
7950fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
7960fab45107f043a4856aad2e1fd785b396898616eChris Craik
7970ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            // Tests may rely on bound holders not being shared between inner adapters,
7980ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            // since we force recycle here
7990ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            holder.mRecyclerView.swapAdapter(mAdapters.get(position), true);
8000ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8010ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            Parcelable savedState = mSavedStates.get(position);
8020ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            if (savedState != null) {
8030ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
8040ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                mSavedStates.set(position, null);
8050ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            }
8060ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        }
8070ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8080ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        @Override
8090ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        public void onViewRecycled(ViewHolder holder) {
8100ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik            mSavedStates.set(holder.getAdapterPosition(),
8110ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik                    holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
8121e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8131e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8141e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        @Override
8151e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        public int getItemCount() {
8168123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            return mAdapters.size();
8171e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        }
8181e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
8191e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8201e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    @Test
8210ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchSimple() {
8221e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
823d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik        assertEquals(2, llm.getInitialPrefetchItemCount());
8241e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8251e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setLayoutManager(llm);
8261e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.setAdapter(new OuterAdapter());
8271e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8281e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        layout(200, 200);
8291e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
8301e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8311e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // prefetch 2 (default)
8321e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8331e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
8341e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertNotNull(holder);
8351e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        assertNotNull(holder.mNestedRecyclerView);
8368123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        RecyclerView innerView = holder.mNestedRecyclerView.get();
8378123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
8381e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik
8391e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        // prefetch 4
8408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        ((LinearLayoutManager) innerView.getLayoutManager())
8411e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik                .setInitialPrefetchItemCount(4);
8421e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8438123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3);
8441e0a453dff8d7bb7a966d006541454c770d1ed05Chris Craik    }
8450ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
8460ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    @Test
8470fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    public void nestedPrefetchNotClearInnerStructureChangeFlag() {
8480fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        LinearLayoutManager llm = new LinearLayoutManager(getContext());
8490fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertEquals(2, llm.getInitialPrefetchItemCount());
8500fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8510fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.setLayoutManager(llm);
8520fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.setAdapter(new OuterAdapter());
8530fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8540fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
8550fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
8560fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8570fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch 2 (default)
8580fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8590fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
8600fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertNotNull(holder);
8610fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertNotNull(holder.mNestedRecyclerView);
8620fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView innerView = holder.mNestedRecyclerView.get();
8630fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        RecyclerView.Adapter innerAdapter = innerView.getAdapter();
8640fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
8650fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // mStructureChanged is initially true before first layout pass.
8660fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
8670fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
8680fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8690fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // layout position 2 and clear mStructureChanged
8700fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.scrollToPosition(2);
8710fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
8720fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.scrollToPosition(0);
8730fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        layout(200, 200);
8740fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertFalse(innerView.mState.mStructureChanged);
8750fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertFalse(innerView.hasPendingAdapterUpdates());
8760fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8770fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // notify change on the cached innerView.
8780fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        innerAdapter.notifyDataSetChanged();
8790fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
8800fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
8810fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8820fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch again
8830fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
8840fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        ((LinearLayoutManager) innerView.getLayoutManager())
8850fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu                .setInitialPrefetchItemCount(2);
8860fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
8870fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
8880fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8890fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // The re-prefetch is not necessary get the same inner view but we will get same Adapter
8900fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
8910fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        innerView = holder.mNestedRecyclerView.get();
8920fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertSame(innerAdapter, innerView.getAdapter());
8930fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        // prefetch shouldn't clear the mStructureChanged flag
8940fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.mState.mStructureChanged);
8950fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu        assertTrue(innerView.hasPendingAdapterUpdates());
8960fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    }
8970fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu
8980fd482dee2c8c2aa61e59d6501e46864e16fcb04Dake Gu    @Test
8990ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchReverseInner() {
9000ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9010ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setAdapter(new OuterAdapter(/* reverseInner = */ true));
9020ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9030ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        layout(200, 200);
9040ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
9050ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9060ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9070ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9080ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9090ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // anchor from right side, should see last two positions
9108123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19);
9110ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    }
9120ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9130ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    @Test
9140ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    public void nestedPrefetchOffset() {
9150ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9160ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.setAdapter(new OuterAdapter());
9170ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9180ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        layout(200, 200);
9190ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9200ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // Scroll top row by 5.5 items, verify positions 5, 6, 7 showing
9210ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        RecyclerView inner = (RecyclerView) mRecyclerView.getChildAt(0);
9220ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        inner.scrollBy(550, 0);
9230ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(5, RecyclerView.getChildViewHolderInt(inner.getChildAt(0)).mPosition);
9240ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(6, RecyclerView.getChildViewHolderInt(inner.getChildAt(1)).mPosition);
9250ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        assertEquals(7, RecyclerView.getChildViewHolderInt(inner.getChildAt(2)).mPosition);
9260ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9270ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // scroll down 4 rows, up 3 so row 0 is adjacent but uncached
9280ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.scrollBy(0, 400);
9290ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.scrollBy(0, -300);
9300ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9310ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // top row no longer present
9320ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheDoesNotContainPositions(mRecyclerView, 0);
9330ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9340ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // prefetch upward, and validate that we've gotten the top row with correct offsets
9350ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
9360ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9370ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        inner = (RecyclerView) CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0).itemView;
9380ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6);
9390ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik
9400ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        // prefetch 4
9410ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        ((LinearLayoutManager) inner.getLayoutManager()).setInitialPrefetchItemCount(4);
9420ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
9430ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(inner, 5, 6, 7, 8);
9440ceae57ee14da826c31a66c67e70f7f17108d5caChris Craik    }
945f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
946f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    @Test
947f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    public void nestedPrefetchNotReset() {
948f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
949f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        OuterAdapter outerAdapter = new OuterAdapter();
950f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.setAdapter(outerAdapter);
951f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
952f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        layout(200, 200);
953f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
954f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
955f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
956f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // prefetch row 2, items 0 & 1
957f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound);
958f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
959f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
9608123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
961f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
962f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertNotNull(innerRecyclerView);
963f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1);
964f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound);
965f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
966f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // new row comes on, triggers layout...
967f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        mRecyclerView.scrollBy(0, 50);
968f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik
969f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // ... which shouldn't require new items to be bound,
970f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        // as prefetch has already done that work
971f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik        assertEquals(2, outerAdapter.mAdapters.get(2).mItemsBound);
972f2f73b7cc9bed0e7204ba43796bb0112cda27cf1Chris Craik    }
9730fab45107f043a4856aad2e1fd785b396898616eChris Craik
9740fab45107f043a4856aad2e1fd785b396898616eChris Craik    static void validateRvChildrenValid(RecyclerView recyclerView, int childCount) {
9750fab45107f043a4856aad2e1fd785b396898616eChris Craik        ChildHelper childHelper = recyclerView.mChildHelper;
9760fab45107f043a4856aad2e1fd785b396898616eChris Craik
9770fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(childCount, childHelper.getUnfilteredChildCount());
9780fab45107f043a4856aad2e1fd785b396898616eChris Craik        for (int i = 0; i < childHelper.getUnfilteredChildCount(); i++) {
9790fab45107f043a4856aad2e1fd785b396898616eChris Craik            assertFalse(recyclerView.getChildViewHolder(
9800fab45107f043a4856aad2e1fd785b396898616eChris Craik                    childHelper.getUnfilteredChildAt(i)).isInvalid());
9810fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
9820fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
9830fab45107f043a4856aad2e1fd785b396898616eChris Craik
9840fab45107f043a4856aad2e1fd785b396898616eChris Craik    @Test
9850fab45107f043a4856aad2e1fd785b396898616eChris Craik    public void nestedPrefetchCacheNotTouched() {
9860fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
9870fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterAdapter outerAdapter = new OuterAdapter();
9880fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setAdapter(outerAdapter);
9890fab45107f043a4856aad2e1fd785b396898616eChris Craik
9900fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 200);
9910fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.scrollBy(0, 100);
9920fab45107f043a4856aad2e1fd785b396898616eChris Craik
9930fab45107f043a4856aad2e1fd785b396898616eChris Craik        // item 0 is cached
9940fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
9950fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0);
9968123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
9970fab45107f043a4856aad2e1fd785b396898616eChris Craik
9980fab45107f043a4856aad2e1fd785b396898616eChris Craik        // try and prefetch it
9990fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
10000fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
10010fab45107f043a4856aad2e1fd785b396898616eChris Craik
10020fab45107f043a4856aad2e1fd785b396898616eChris Craik        // make sure cache's inner items aren't rebound unnecessarily
10030fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
10048123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
10058123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    }
10068123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10078123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    @Test
10088123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik    public void nestedRemoveAnimatingView() {
10098123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10108123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        OuterAdapter outerAdapter = new OuterAdapter(false, 1);
10118123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.setAdapter(outerAdapter);
10128123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30));
10138123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10148123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        layout(200, 200);
10158123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10168123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // Insert 3 items - only first one in viewport, so only it animates
10178123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        for (int i = 0; i < 3; i++) {
10188123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            outerAdapter.addItem();
10198123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
10208123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        layout(200, 200); // layout again to kick off animation
10218123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10228123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10238123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // item 1 is animating, so scroll it out of viewport
10248123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.scrollBy(0, 200);
10258123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10268123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for...
10278123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
10288123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(1, mRecycler.mCachedViews.size());
10298123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0);
10308123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
10318123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10328123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        // until animation ends
10338123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        mRecyclerView.getItemAnimator().endAnimations();
10348123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
10358123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(2, mRecycler.mCachedViews.size());
10368123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1);
10378123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
10388123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik
10398123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) {
10408123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            assertNotNull(viewHolder.mNestedRecyclerView);
10418123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        }
10420fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
10430fab45107f043a4856aad2e1fd785b396898616eChris Craik
10449ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    @Test
10459ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    public void nestedExpandCacheCorrectly() {
10469ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE;
10479ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10489ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
10499ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        OuterAdapter outerAdapter = new OuterAdapter();
10509ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.setAdapter(outerAdapter);
10519ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10529ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        layout(200, 200);
10539ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10549ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
10559ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
10569ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10579ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        // after initial prefetch, view cache max expanded by number of inner items prefetched (2)
10589ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
10599ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        RecyclerView innerView = holder.mNestedRecyclerView.get();
10609ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
10619ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved);
10629ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
10639ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10649ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        try {
10659ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // Note: As a hack, we not only must manually dispatch attachToWindow(), but we
10669ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker
10679ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // is registered to the wrong thread, since @setup is called on a different thread
10689ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // from @Test. Assert this, so this test can be fixed when setup == test thread.
10699ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size());
10709ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.isAttachedToWindow());
10719ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.onAttachedToWindow();
10729ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10739ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // bring prefetch view into viewport, at which point it shouldn't have cache expanded...
10749ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            mRecyclerView.scrollBy(0, 100);
10759ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
10769ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved);
10779ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
10789ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10799ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            // until a valid horizontal prefetch caches an item, and expands view count by one
10809ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.mPrefetchRegistry.setPrefetchVector(1, 0);
10819ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker
10829ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
10839ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved);
10849ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
10859ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        } finally {
10869ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            if (innerView.isAttachedToWindow()) {
10879ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik                innerView.onDetachedFromWindow();
10889ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik            }
10899ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik        }
10909ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik    }
10919ad077f4ba751b392e6f4454b09c2305cdc22b0dChris Craik
10920fab45107f043a4856aad2e1fd785b396898616eChris Craik    /**
10930fab45107f043a4856aad2e1fd785b396898616eChris Craik     * Similar to OuterAdapter above, but uses notifyDataSetChanged() instead of set/swapAdapter
10940fab45107f043a4856aad2e1fd785b396898616eChris Craik     * to update data for the inner RecyclerViews when containing ViewHolder is bound.
10950fab45107f043a4856aad2e1fd785b396898616eChris Craik     */
10960fab45107f043a4856aad2e1fd785b396898616eChris Craik    class OuterNotifyAdapter extends RecyclerView.Adapter<OuterNotifyAdapter.ViewHolder> {
10970fab45107f043a4856aad2e1fd785b396898616eChris Craik        private static final int OUTER_ITEM_COUNT = 10;
10980fab45107f043a4856aad2e1fd785b396898616eChris Craik
10990fab45107f043a4856aad2e1fd785b396898616eChris Craik        private boolean mReverseInner;
11000fab45107f043a4856aad2e1fd785b396898616eChris Craik
11010fab45107f043a4856aad2e1fd785b396898616eChris Craik        class ViewHolder extends RecyclerView.ViewHolder {
11020fab45107f043a4856aad2e1fd785b396898616eChris Craik            private final RecyclerView mRecyclerView;
11030fab45107f043a4856aad2e1fd785b396898616eChris Craik            private final InnerAdapter mAdapter;
11040fab45107f043a4856aad2e1fd785b396898616eChris Craik            ViewHolder(RecyclerView itemView) {
11050fab45107f043a4856aad2e1fd785b396898616eChris Craik                super(itemView);
11060fab45107f043a4856aad2e1fd785b396898616eChris Craik                mRecyclerView = itemView;
11070fab45107f043a4856aad2e1fd785b396898616eChris Craik                mAdapter = new InnerAdapter();
11080fab45107f043a4856aad2e1fd785b396898616eChris Craik                mRecyclerView.setAdapter(mAdapter);
11090fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11100fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11110fab45107f043a4856aad2e1fd785b396898616eChris Craik
11120fab45107f043a4856aad2e1fd785b396898616eChris Craik        ArrayList<Parcelable> mSavedStates = new ArrayList<>();
11130fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();
11140fab45107f043a4856aad2e1fd785b396898616eChris Craik
11150fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter() {
11160fab45107f043a4856aad2e1fd785b396898616eChris Craik            this(false);
11170fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11180fab45107f043a4856aad2e1fd785b396898616eChris Craik
11190fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter(boolean reverseInner) {
11200fab45107f043a4856aad2e1fd785b396898616eChris Craik            mReverseInner = reverseInner;
11210fab45107f043a4856aad2e1fd785b396898616eChris Craik            for (int i = 0; i <= OUTER_ITEM_COUNT; i++) {
11220fab45107f043a4856aad2e1fd785b396898616eChris Craik                mSavedStates.add(null);
11230fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11240fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11250fab45107f043a4856aad2e1fd785b396898616eChris Craik
11260fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11270fab45107f043a4856aad2e1fd785b396898616eChris Craik        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
11280fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
11290fab45107f043a4856aad2e1fd785b396898616eChris Craik            RecyclerView rv = new RecyclerView(parent.getContext());
11300fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setLayoutManager(new LinearLayoutManager(parent.getContext(),
11310fab45107f043a4856aad2e1fd785b396898616eChris Craik                    LinearLayoutManager.HORIZONTAL, mReverseInner));
11320fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setRecycledViewPool(mSharedPool);
11330fab45107f043a4856aad2e1fd785b396898616eChris Craik            rv.setLayoutParams(new RecyclerView.LayoutParams(200, 100));
11340fab45107f043a4856aad2e1fd785b396898616eChris Craik            return new ViewHolder(rv);
11350fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11360fab45107f043a4856aad2e1fd785b396898616eChris Craik
11370fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11380fab45107f043a4856aad2e1fd785b396898616eChris Craik        public void onBindViewHolder(ViewHolder holder, int position) {
11390fab45107f043a4856aad2e1fd785b396898616eChris Craik            mRecyclerView.registerTimePassingMs(5);
11400fab45107f043a4856aad2e1fd785b396898616eChris Craik            // if we had actual data to put into our adapter, this is where we'd do it...
11410fab45107f043a4856aad2e1fd785b396898616eChris Craik
11420fab45107f043a4856aad2e1fd785b396898616eChris Craik            // ... then notify the adapter that it has new content:
11430fab45107f043a4856aad2e1fd785b396898616eChris Craik            holder.mAdapter.notifyDataSetChanged();
11440fab45107f043a4856aad2e1fd785b396898616eChris Craik
11450fab45107f043a4856aad2e1fd785b396898616eChris Craik            Parcelable savedState = mSavedStates.get(position);
11460fab45107f043a4856aad2e1fd785b396898616eChris Craik            if (savedState != null) {
11470fab45107f043a4856aad2e1fd785b396898616eChris Craik                holder.mRecyclerView.getLayoutManager().onRestoreInstanceState(savedState);
11480fab45107f043a4856aad2e1fd785b396898616eChris Craik                mSavedStates.set(position, null);
11490fab45107f043a4856aad2e1fd785b396898616eChris Craik            }
11500fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11510fab45107f043a4856aad2e1fd785b396898616eChris Craik
11520fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11530fab45107f043a4856aad2e1fd785b396898616eChris Craik        public void onViewRecycled(ViewHolder holder) {
1154570ab881350c836743247140a0953f87acc21c8dChris Craik            if (holder.getAdapterPosition() >= 0) {
1155570ab881350c836743247140a0953f87acc21c8dChris Craik                mSavedStates.set(holder.getAdapterPosition(),
1156570ab881350c836743247140a0953f87acc21c8dChris Craik                        holder.mRecyclerView.getLayoutManager().onSaveInstanceState());
1157570ab881350c836743247140a0953f87acc21c8dChris Craik            }
11580fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11590fab45107f043a4856aad2e1fd785b396898616eChris Craik
11600fab45107f043a4856aad2e1fd785b396898616eChris Craik        @Override
11610fab45107f043a4856aad2e1fd785b396898616eChris Craik        public int getItemCount() {
11620fab45107f043a4856aad2e1fd785b396898616eChris Craik            return OUTER_ITEM_COUNT;
11630fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11640fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
11650fab45107f043a4856aad2e1fd785b396898616eChris Craik
11660fab45107f043a4856aad2e1fd785b396898616eChris Craik    @Test
11670fab45107f043a4856aad2e1fd785b396898616eChris Craik    public void nestedPrefetchDiscardStaleChildren() {
11680fab45107f043a4856aad2e1fd785b396898616eChris Craik        LinearLayoutManager llm = new LinearLayoutManager(getContext());
1169d6696c2abea2771acd000c2269cf9113acc6c0a9Chris Craik        assertEquals(2, llm.getInitialPrefetchItemCount());
11700fab45107f043a4856aad2e1fd785b396898616eChris Craik
11710fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setLayoutManager(llm);
11720fab45107f043a4856aad2e1fd785b396898616eChris Craik        OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter();
11730fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setAdapter(outerAdapter);
11740fab45107f043a4856aad2e1fd785b396898616eChris Craik
11750fab45107f043a4856aad2e1fd785b396898616eChris Craik        // zero cache, so item we prefetch can't already be ready
11760fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.setItemViewCacheSize(0);
11770fab45107f043a4856aad2e1fd785b396898616eChris Craik
11780fab45107f043a4856aad2e1fd785b396898616eChris Craik        // layout 3 items, then resize to 2...
11790fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 300);
11800fab45107f043a4856aad2e1fd785b396898616eChris Craik        layout(200, 200);
11810fab45107f043a4856aad2e1fd785b396898616eChris Craik
11820fab45107f043a4856aad2e1fd785b396898616eChris Craik        // so 1 item is evicted into the RecycledViewPool (bypassing cache)
11830fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(1, mRecycler.mRecyclerPool.getRecycledViewCount(0));
11840fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertEquals(0, mRecycler.mCachedViews.size());
11850fab45107f043a4856aad2e1fd785b396898616eChris Craik
11860fab45107f043a4856aad2e1fd785b396898616eChris Craik        // This is a simple imitation of other behavior (namely, varied types in the outer adapter)
11870fab45107f043a4856aad2e1fd785b396898616eChris Craik        // that results in the same initial state to test: items in the pool with attached children
11880fab45107f043a4856aad2e1fd785b396898616eChris Craik        for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) {
11890fab45107f043a4856aad2e1fd785b396898616eChris Craik            // verify that children are attached and valid, since the RVs haven't been rebound
11900fab45107f043a4856aad2e1fd785b396898616eChris Craik            assertNotNull(holder.mNestedRecyclerView);
11918123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout);
11928123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik            validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
11930fab45107f043a4856aad2e1fd785b396898616eChris Craik        }
11940fab45107f043a4856aad2e1fd785b396898616eChris Craik
11950fab45107f043a4856aad2e1fd785b396898616eChris Craik        // prefetch the outer item bind, but without enough time to do any inner binds
11960fab45107f043a4856aad2e1fd785b396898616eChris Craik        final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(9);
11970fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
11980fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(deadlineNs);
11990fab45107f043a4856aad2e1fd785b396898616eChris Craik
12000fab45107f043a4856aad2e1fd785b396898616eChris Craik        // 2 is prefetched without children
12010fab45107f043a4856aad2e1fd785b396898616eChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 2);
12020fab45107f043a4856aad2e1fd785b396898616eChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
12030fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertNotNull(holder);
12040fab45107f043a4856aad2e1fd785b396898616eChris Craik        assertNotNull(holder.mNestedRecyclerView);
12058123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount());
12068123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size());
12070fab45107f043a4856aad2e1fd785b396898616eChris Craik
12080fab45107f043a4856aad2e1fd785b396898616eChris Craik        // but if we give it more time to bind items, it'll now acquire its inner items
12090fab45107f043a4856aad2e1fd785b396898616eChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
12108123166eaf758eacc6ba5854c9b29d99f21388d6Chris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1);
12110fab45107f043a4856aad2e1fd785b396898616eChris Craik    }
1212570ab881350c836743247140a0953f87acc21c8dChris Craik
1213570ab881350c836743247140a0953f87acc21c8dChris Craik
1214570ab881350c836743247140a0953f87acc21c8dChris Craik    @Test
1215570ab881350c836743247140a0953f87acc21c8dChris Craik    public void nestedPrefetchDiscardStalePrefetch() {
1216570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
1217570ab881350c836743247140a0953f87acc21c8dChris Craik        OuterNotifyAdapter outerAdapter = new OuterNotifyAdapter();
1218570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setAdapter(outerAdapter);
1219570ab881350c836743247140a0953f87acc21c8dChris Craik
1220570ab881350c836743247140a0953f87acc21c8dChris Craik        // zero cache, so item we prefetch can't already be ready
1221570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.setItemViewCacheSize(0);
1222570ab881350c836743247140a0953f87acc21c8dChris Craik
1223570ab881350c836743247140a0953f87acc21c8dChris Craik        // layout as 2x2, starting on row index 2, with empty cache
1224570ab881350c836743247140a0953f87acc21c8dChris Craik        layout(200, 200);
1225570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.scrollBy(0, 200);
1226570ab881350c836743247140a0953f87acc21c8dChris Craik
1227570ab881350c836743247140a0953f87acc21c8dChris Craik        // no views cached, or previously used (so we can trust number in mItemsBound)
1228570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecycler.mRecyclerPool.clear();
1229570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, mRecycler.mRecyclerPool.getRecycledViewCount(0));
1230570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, mRecycler.mCachedViews.size());
1231570ab881350c836743247140a0953f87acc21c8dChris Craik
1232570ab881350c836743247140a0953f87acc21c8dChris Craik        // prefetch the outer item and its inner children
1233570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1234570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1235570ab881350c836743247140a0953f87acc21c8dChris Craik
1236570ab881350c836743247140a0953f87acc21c8dChris Craik        // 4 is prefetched with 2 inner children, first two binds
1237570ab881350c836743247140a0953f87acc21c8dChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4);
1238570ab881350c836743247140a0953f87acc21c8dChris Craik        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4);
1239570ab881350c836743247140a0953f87acc21c8dChris Craik        assertNotNull(holder);
1240570ab881350c836743247140a0953f87acc21c8dChris Craik        assertNotNull(holder.mNestedRecyclerView);
1241570ab881350c836743247140a0953f87acc21c8dChris Craik        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
1242570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount());
1243570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size());
1244570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound);
1245570ab881350c836743247140a0953f87acc21c8dChris Craik
1246570ab881350c836743247140a0953f87acc21c8dChris Craik        // notify data set changed, so any previously prefetched items invalid, and re-prefetch
1247570ab881350c836743247140a0953f87acc21c8dChris Craik        innerRecyclerView.getAdapter().notifyDataSetChanged();
1248570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
1249570ab881350c836743247140a0953f87acc21c8dChris Craik        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
1250570ab881350c836743247140a0953f87acc21c8dChris Craik
1251570ab881350c836743247140a0953f87acc21c8dChris Craik        // 4 is prefetched again...
1252570ab881350c836743247140a0953f87acc21c8dChris Craik        CacheUtils.verifyCacheContainsPrefetchedPositions(mRecyclerView, 4);
1253570ab881350c836743247140a0953f87acc21c8dChris Craik
1254570ab881350c836743247140a0953f87acc21c8dChris Craik        // reusing the same instance with 2 inner children...
1255570ab881350c836743247140a0953f87acc21c8dChris Craik        assertSame(holder, CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 4));
1256570ab881350c836743247140a0953f87acc21c8dChris Craik        assertSame(innerRecyclerView, holder.mNestedRecyclerView.get());
1257570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(0, innerRecyclerView.mChildHelper.getUnfilteredChildCount());
1258570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(2, innerRecyclerView.mRecycler.mCachedViews.size());
1259570ab881350c836743247140a0953f87acc21c8dChris Craik
1260570ab881350c836743247140a0953f87acc21c8dChris Craik        // ... but there should be two new binds
1261570ab881350c836743247140a0953f87acc21c8dChris Craik        assertEquals(4, ((InnerAdapter) innerRecyclerView.getAdapter()).mItemsBound);
1262570ab881350c836743247140a0953f87acc21c8dChris Craik    }
1263945dc1e8a9ee0b71e6b2454ccb3e61145d2132f5Chris Craik}
1264