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