1a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev/*
2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project
3a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev *
4a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * Licensed under the Apache License, Version 2.0 (the "License");
5a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * you may not use this file except in compliance with the License.
6a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * You may obtain a copy of the License at
7a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev *
8a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev *      http://www.apache.org/licenses/LICENSE-2.0
9a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev *
10a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * Unless required by applicable law or agreed to in writing, software
11a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * distributed under the License is distributed on an "AS IS" BASIS,
12a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * See the License for the specific language governing permissions and
14a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * limitations under the License.
15a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev */
16a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.recyclerview.widget;
18a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
19754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport static org.junit.Assert.assertEquals;
20754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport static org.junit.Assert.assertTrue;
21754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikas
22754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport static java.util.concurrent.TimeUnit.SECONDS;
230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar
24a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.content.Context;
25754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport android.support.test.filters.MediumTest;
2641b575f39027c3ab74d8368d3ff8dd121443cd2eAurimas Liutikasimport android.support.test.filters.Suppress;
270a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.support.test.runner.AndroidJUnit4;
28a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.view.View;
29a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.view.ViewGroup;
30a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.widget.TextView;
31a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
32c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikasimport androidx.annotation.NonNull;
33c95a6f1f125ad3a7e1f9f79bccf4b2603bc40ebaAurimas Liutikas
34754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport org.hamcrest.CoreMatchers;
35754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport org.hamcrest.MatcherAssert;
36754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport org.junit.Test;
37754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikasimport org.junit.runner.RunWith;
38754cb29c50f09a83251dd4bb633ba445b2411adbAurimas Liutikas
39a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.BitSet;
40a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.concurrent.CountDownLatch;
41a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.concurrent.TimeUnit;
4266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar
43f1b288ec2104488f4a92e911b0ab80c8f0f3e9d1Yigit Boyar@MediumTest
440a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar@RunWith(AndroidJUnit4.class)
45a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevpublic class AsyncListUtilLayoutTest extends BaseRecyclerViewInstrumentationTest {
46a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
47a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private static final boolean DEBUG = false;
48a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
49a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private static final String TAG = "AsyncListUtilLayoutTest";
50a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
51a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private static final int ITEM_COUNT = 1000;
52a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private static final int TILE_SIZE = 5;
53a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
54a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    AsyncTestAdapter mAdapter;
55a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
56a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    WrappedLinearLayoutManager mLayoutManager;
57a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
58a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private TestDataCallback mDataCallback;
59a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private TestViewCallback mViewCallback;
60a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private AsyncListUtil<String> mAsyncListUtil;
61a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
62a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    public int mStartPrefetch = 0;
63a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    public int mEndPrefetch = 0;
64a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
6541b575f39027c3ab74d8368d3ff8dd121443cd2eAurimas Liutikas    // Test is disabled as it is flaky.
6641b575f39027c3ab74d8368d3ff8dd121443cd2eAurimas Liutikas    @Suppress
670a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    @Test
680a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar    public void asyncListUtil() throws Throwable {
69a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mRecyclerView = inflateWrappedRV();
70a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mRecyclerView.setHasFixedSize(true);
71a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
72a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mAdapter = new AsyncTestAdapter();
73a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mRecyclerView.setAdapter(mAdapter);
74a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
75a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mLayoutManager = new WrappedLinearLayoutManager(
76a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                getActivity(), LinearLayoutManager.VERTICAL, false);
77a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mRecyclerView.setLayoutManager(mLayoutManager);
78a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
79a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mLayoutManager.expectLayouts(1);
80a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        setRecyclerView(mRecyclerView);
81a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mLayoutManager.waitForLayout(2);
82a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
83a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        int rangeStart = 0;
84a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        assertEquals(rangeStart, mLayoutManager.findFirstVisibleItemPosition());
85a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
86a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        final int rangeSize = mLayoutManager.findLastVisibleItemPosition() + 1;
87a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        assertTrue("No visible items", rangeSize > 0);
88a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
89a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        assertEquals("All visible items must be empty at first",
90a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                rangeSize, getEmptyVisibleChildCount());
91a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
92a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mDataCallback = new TestDataCallback();
93a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mViewCallback = new TestViewCallback();
94a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
95a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mDataCallback.expectTilesInRange(rangeStart, rangeSize);
96a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mAdapter.expectItemsInRange(rangeStart, rangeSize);
97a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
9842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
99de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev            @Override
100de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev            public void run() {
101bf9b3e30975fa1f19733893bd6ead0e88bc8e584Yigit Boyar                mAsyncListUtil = new AsyncListUtil<>(
102de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev                        String.class, TILE_SIZE, mDataCallback, mViewCallback);
103de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev            }
104de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev        });
105a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
106a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
107a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            @Override
108a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
109a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                mAsyncListUtil.onRangeChanged();
110a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
111a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        });
112a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        assertAllLoaded("First load");
113a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
114a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        rangeStart = roundUp(rangeSize);
115a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        scrollAndAssert("Scroll with no prefetch", rangeStart, rangeSize);
116a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
117a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        rangeStart = roundUp(rangeStart + rangeSize);
118a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mEndPrefetch = TILE_SIZE * 2;
119a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        scrollAndAssert("Scroll with prefetch", rangeStart, rangeSize);
120a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
121a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        rangeStart += mEndPrefetch;
122a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mEndPrefetch = 0;
123a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        scrollAndAssert("Scroll a little down, no prefetch", rangeStart, 0);
124a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
125a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        rangeStart = ITEM_COUNT / 2;
126a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mStartPrefetch = TILE_SIZE * 2;
127a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mEndPrefetch = TILE_SIZE * 3;
128a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        scrollAndAssert("Scroll to middle, prefetch", rangeStart, rangeSize);
129a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
130a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        rangeStart -= mStartPrefetch;
131a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mStartPrefetch = 0;
132a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mEndPrefetch = 0;
133a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        scrollAndAssert("Scroll a little up, no prefetch", rangeStart, 0);
134a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
135a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        Thread.sleep(500);  // Wait for possible spurious messages.
136a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
137a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
138bf9b3e30975fa1f19733893bd6ead0e88bc8e584Yigit Boyar    private void assertAllLoaded(String context) throws InterruptedException {
139bf9b3e30975fa1f19733893bd6ead0e88bc8e584Yigit Boyar        assertTrue(context + ", timed out while waiting for items", mAdapter.waitForItems(10));
140bf9b3e30975fa1f19733893bd6ead0e88bc8e584Yigit Boyar        assertTrue(context + ", timed out while waiting for tiles", mDataCallback.waitForTiles(10));
141a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        assertEquals(context + ", empty child found", 0, getEmptyVisibleChildCount());
142a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
143a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
144a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private void scrollAndAssert(String context, int rangeStart, int rangeSize) throws Throwable {
145a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        if (rangeSize > 0) {
146a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mDataCallback.expectTilesInRange(rangeStart, rangeSize);
147a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        } else {
148a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mDataCallback.expectNoNewTilesLoaded();
149a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
150a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mAdapter.expectItemsInRange(rangeStart, rangeSize);
151a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mLayoutManager.expectLayouts(1);
152a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        scrollToPositionWithOffset(rangeStart, 0);
153a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        mLayoutManager.waitForLayout(1);
154a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        assertAllLoaded(context);
155a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
156a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
157a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
15842e7d6fafcde7bfe261dd7d8d75ee53ca0cd6790Aurimas Liutikas        mActivityRule.runOnUiThread(new Runnable() {
159a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            @Override
160a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            public void run() {
161a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                mLayoutManager.scrollToPositionWithOffset(position, offset);
162a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
163a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        });
164a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
165a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
166a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private int roundUp(int value) {
167a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        return value - value % TILE_SIZE + TILE_SIZE;
168a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
169a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
170a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private int getTileCount(int start, int size) {
171a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        return ((start + size - 1) / TILE_SIZE) - (start / TILE_SIZE) + 1;
172a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
173a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
174a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private int getEmptyVisibleChildCount() {
175a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        int emptyChildCount = 0;
176a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        int firstVisible = mLayoutManager.findFirstVisibleItemPosition();
177a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        int lastVisible = mLayoutManager.findLastVisibleItemPosition();
178a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        for (int i = firstVisible; i <= lastVisible; i++) {
179a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            View child = mLayoutManager.findViewByPosition(i);
180a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            assertTrue(child instanceof TextView);
181a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            if (((TextView) child).getText() == "") {
182a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                emptyChildCount++;
183a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
184a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
185a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        return emptyChildCount;
186a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
187a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
188a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private class TestDataCallback extends AsyncListUtil.DataCallback<String> {
189a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
190a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        private CountDownLatch mTilesLatch;
191a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
192a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
193a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void fillData(String[] data, int startPosition, int itemCount) {
194a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            assertTrue("Unexpected tile load @" + startPosition, mTilesLatch.getCount() > 0);
195a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            try {
196a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                Thread.sleep(100);
197a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            } catch (InterruptedException e) {
198a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
199a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            for (int i = 0; i < itemCount; i++) {
200a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                data[i] = "Item #" + (startPosition + i);
201a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
202a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mTilesLatch.countDown();
203a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
204a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
205a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
206a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public int refreshData() {
207a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            return ITEM_COUNT;
208a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
209a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
210a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        private void expectTiles(int count) {
211a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mTilesLatch = new CountDownLatch(count);
212a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
213a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
214a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void expectTilesInRange(int rangeStart, int rangeSize) {
215a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            expectTiles(getTileCount(rangeStart - mStartPrefetch,
216a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                    rangeSize + mStartPrefetch + mEndPrefetch));
217a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
218a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
219a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void expectNoNewTilesLoaded() {
220a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            expectTiles(0);
221a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
222a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
223a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public boolean waitForTiles(long timeoutInSeconds) throws InterruptedException {
224a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            return mTilesLatch.await(timeoutInSeconds, TimeUnit.SECONDS);
225a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
226a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
227a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
228a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private class TestViewCallback extends AsyncListUtil.ViewCallback {
229a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
230a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void getItemRangeInto(int[] outRange) {
231a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            outRange[0] = mLayoutManager.findFirstVisibleItemPosition();
232a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            outRange[1] = mLayoutManager.findLastVisibleItemPosition();
233a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
234a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
235a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
236a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void extendRangeInto(int[] range, int[] outRange, int scrollHint) {
237a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            outRange[0] = range[0] - mStartPrefetch;
238a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            outRange[1] = range[1] + mEndPrefetch;
239a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
240a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
241a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
242a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void onDataRefresh() {
243a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mRecyclerView.getAdapter().notifyDataSetChanged();
244a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
245a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
246a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
247a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void onItemLoaded(int position) {
248a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mRecyclerView.getAdapter().notifyItemChanged(position);
249a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
250a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
251a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
252a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private static class SimpleViewHolder extends RecyclerView.ViewHolder {
253a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
254a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public SimpleViewHolder(Context context) {
255a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            super(new TextView(context));
256a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
257a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
258a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
259a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    private class AsyncTestAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
260a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
261a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        private BitSet mLoadedPositions;
262a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        private BitSet mExpectedPositions;
263a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
264a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        private CountDownLatch mItemsLatch;
265a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public AsyncTestAdapter() {
266a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mLoadedPositions = new BitSet(ITEM_COUNT);
267a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
268a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
269a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
2708a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
271a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            return new SimpleViewHolder(parent.getContext());
272a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
273a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
274a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
2758a11e6829c522aa1efcc903afa4c01d337082eabChris Craik        public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position) {
276a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            final String item = mAsyncListUtil == null ? null : mAsyncListUtil.getItem(position);
277a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            ((TextView) (holder.itemView)).setText(item == null ? "" : item);
278a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
279a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            if (item != null) {
280a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                mLoadedPositions.set(position);
281a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                if (mExpectedPositions.get(position)) {
282a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                    mExpectedPositions.clear(position);
283a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                    if (mExpectedPositions.cardinality() == 0) {
284a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                        mItemsLatch.countDown();
285a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                    }
286a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                }
287a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
288a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
289a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
290a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
291a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public int getItemCount() {
292a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            return ITEM_COUNT;
293a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
294a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
295a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        private void expectItemsInRange(int rangeStart, int rangeSize) {
296a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mExpectedPositions = new BitSet(rangeStart + rangeSize);
297a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            for (int i = 0; i < rangeSize; i++) {
298a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                if (!mLoadedPositions.get(rangeStart + i)) {
299a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                    mExpectedPositions.set(rangeStart + i);
300a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                }
301a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
302a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mItemsLatch = new CountDownLatch(1);
303a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            if (mExpectedPositions.cardinality() == 0) {
304a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                mItemsLatch.countDown();
305a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
306a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
307a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
308a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public boolean waitForItems(long timeoutInSeconds) throws InterruptedException {
309a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            return mItemsLatch.await(timeoutInSeconds, TimeUnit.SECONDS);
310a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
311a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
312a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
313a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    class WrappedLinearLayoutManager extends LinearLayoutManager {
314a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
315a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        CountDownLatch mLayoutLatch;
316a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
317a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
318a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            super(context, orientation, reverseLayout);
319a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
320a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
321a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void expectLayouts(int count) {
322a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mLayoutLatch = new CountDownLatch(count);
323a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
324a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
32566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar        public void waitForLayout(int seconds) throws Throwable {
32666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            mLayoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
32766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            checkForMainThreadException();
32866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            MatcherAssert.assertThat("all layouts should complete on time",
32966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                    mLayoutLatch.getCount(), CoreMatchers.is(0L));
33066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            // use a runnable to ensure RV layout is finished
33166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            getInstrumentation().runOnMainSync(new Runnable() {
33266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                @Override
33366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                public void run() {
33466826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar                }
33566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar            });
336a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
337a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev
338a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        @Override
339a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
340a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            try {
341a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                super.onLayoutChildren(recycler, state);
342a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            } catch (Throwable t) {
343a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev                postExceptionToInstrumentation(t);
344a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            }
345a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev            mLayoutLatch.countDown();
346a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev        }
347a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev    }
348a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev}
349