1a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev/* 2a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev * Copyright (C) 2015 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 17a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevpackage android.support.v7.widget; 18a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 1966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyarimport org.hamcrest.CoreMatchers; 2066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyarimport org.hamcrest.MatcherAssert; 210a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.Test; 220a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport org.junit.runner.RunWith; 230a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar 24a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.content.Context; 250a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport android.support.test.runner.AndroidJUnit4; 26a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.support.v7.util.AsyncListUtil; 27f1b288ec2104488f4a92e911b0ab80c8f0f3e9d1Yigit Boyarimport android.test.suitebuilder.annotation.MediumTest; 28a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.view.View; 29a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.view.ViewGroup; 30a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport android.widget.TextView; 31a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 32a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.BitSet; 33a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.concurrent.CountDownLatch; 34a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevimport java.util.concurrent.TimeUnit; 350a017072206f93474ccd2706e7983c2ff778b904Yigit Boyarimport static org.junit.Assert.*; 36a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 3766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyarimport static java.util.concurrent.TimeUnit.SECONDS; 3866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar 39f1b288ec2104488f4a92e911b0ab80c8f0f3e9d1Yigit Boyar@MediumTest 400a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar@RunWith(AndroidJUnit4.class) 41a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheevpublic class AsyncListUtilLayoutTest extends BaseRecyclerViewInstrumentationTest { 42a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 43a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private static final boolean DEBUG = false; 44a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 45a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private static final String TAG = "AsyncListUtilLayoutTest"; 46a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 47a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private static final int ITEM_COUNT = 1000; 48a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private static final int TILE_SIZE = 5; 49a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 50a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev AsyncTestAdapter mAdapter; 51a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 52a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev WrappedLinearLayoutManager mLayoutManager; 53a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 54a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private TestDataCallback mDataCallback; 55a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private TestViewCallback mViewCallback; 56a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private AsyncListUtil<String> mAsyncListUtil; 57a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 58a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public int mStartPrefetch = 0; 59a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public int mEndPrefetch = 0; 60a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 610a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar @Test 620a017072206f93474ccd2706e7983c2ff778b904Yigit Boyar public void asyncListUtil() throws Throwable { 63a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mRecyclerView = inflateWrappedRV(); 64a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mRecyclerView.setHasFixedSize(true); 65a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 66a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mAdapter = new AsyncTestAdapter(); 67a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mRecyclerView.setAdapter(mAdapter); 68a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 69a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLayoutManager = new WrappedLinearLayoutManager( 70a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev getActivity(), LinearLayoutManager.VERTICAL, false); 71a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mRecyclerView.setLayoutManager(mLayoutManager); 72a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 73a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLayoutManager.expectLayouts(1); 74a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev setRecyclerView(mRecyclerView); 75a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLayoutManager.waitForLayout(2); 76a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 77a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev int rangeStart = 0; 78a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertEquals(rangeStart, mLayoutManager.findFirstVisibleItemPosition()); 79a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 80a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev final int rangeSize = mLayoutManager.findLastVisibleItemPosition() + 1; 81a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertTrue("No visible items", rangeSize > 0); 82a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 83a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertEquals("All visible items must be empty at first", 84a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev rangeSize, getEmptyVisibleChildCount()); 85a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 86a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mDataCallback = new TestDataCallback(); 87a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mViewCallback = new TestViewCallback(); 88a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 89a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mDataCallback.expectTilesInRange(rangeStart, rangeSize); 90a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mAdapter.expectItemsInRange(rangeStart, rangeSize); 91a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 92de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev runTestOnUiThread(new Runnable() { 93de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev @Override 94de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev public void run() { 95bf9b3e30975fa1f19733893bd6ead0e88bc8e584Yigit Boyar mAsyncListUtil = new AsyncListUtil<>( 96de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev String.class, TILE_SIZE, mDataCallback, mViewCallback); 97de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev } 98de8e2baf9504defe12972fbf60935a1148f1098fVladislav Kaznacheev }); 99a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 100a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 101a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 102a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 103a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mAsyncListUtil.onRangeChanged(); 104a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 105a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev }); 106a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertAllLoaded("First load"); 107a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 108a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev rangeStart = roundUp(rangeSize); 109a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev scrollAndAssert("Scroll with no prefetch", rangeStart, rangeSize); 110a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 111a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev rangeStart = roundUp(rangeStart + rangeSize); 112a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mEndPrefetch = TILE_SIZE * 2; 113a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev scrollAndAssert("Scroll with prefetch", rangeStart, rangeSize); 114a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 115a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev rangeStart += mEndPrefetch; 116a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mEndPrefetch = 0; 117a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev scrollAndAssert("Scroll a little down, no prefetch", rangeStart, 0); 118a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 119a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev rangeStart = ITEM_COUNT / 2; 120a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mStartPrefetch = TILE_SIZE * 2; 121a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mEndPrefetch = TILE_SIZE * 3; 122a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev scrollAndAssert("Scroll to middle, prefetch", rangeStart, rangeSize); 123a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 124a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev rangeStart -= mStartPrefetch; 125a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mStartPrefetch = 0; 126a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mEndPrefetch = 0; 127a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev scrollAndAssert("Scroll a little up, no prefetch", rangeStart, 0); 128a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 129a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Thread.sleep(500); // Wait for possible spurious messages. 130a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 131a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 132bf9b3e30975fa1f19733893bd6ead0e88bc8e584Yigit Boyar private void assertAllLoaded(String context) throws InterruptedException { 133bf9b3e30975fa1f19733893bd6ead0e88bc8e584Yigit Boyar assertTrue(context + ", timed out while waiting for items", mAdapter.waitForItems(10)); 134bf9b3e30975fa1f19733893bd6ead0e88bc8e584Yigit Boyar assertTrue(context + ", timed out while waiting for tiles", mDataCallback.waitForTiles(10)); 135a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertEquals(context + ", empty child found", 0, getEmptyVisibleChildCount()); 136a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 137a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 138a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private void scrollAndAssert(String context, int rangeStart, int rangeSize) throws Throwable { 139a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev if (rangeSize > 0) { 140a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mDataCallback.expectTilesInRange(rangeStart, rangeSize); 141a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } else { 142a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mDataCallback.expectNoNewTilesLoaded(); 143a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 144a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mAdapter.expectItemsInRange(rangeStart, rangeSize); 145a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLayoutManager.expectLayouts(1); 146a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev scrollToPositionWithOffset(rangeStart, 0); 147a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLayoutManager.waitForLayout(1); 148a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertAllLoaded(context); 149a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 150a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 151a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev void scrollToPositionWithOffset(final int position, final int offset) throws Throwable { 152a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev runTestOnUiThread(new Runnable() { 153a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 154a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void run() { 155a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLayoutManager.scrollToPositionWithOffset(position, offset); 156a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 157a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev }); 158a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 159a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 160a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private int roundUp(int value) { 161a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return value - value % TILE_SIZE + TILE_SIZE; 162a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 163a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 164a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private int getTileCount(int start, int size) { 165a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return ((start + size - 1) / TILE_SIZE) - (start / TILE_SIZE) + 1; 166a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 167a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 168a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private int getEmptyVisibleChildCount() { 169a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev int emptyChildCount = 0; 170a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev int firstVisible = mLayoutManager.findFirstVisibleItemPosition(); 171a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev int lastVisible = mLayoutManager.findLastVisibleItemPosition(); 172a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev for (int i = firstVisible; i <= lastVisible; i++) { 173a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev View child = mLayoutManager.findViewByPosition(i); 174a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertTrue(child instanceof TextView); 175a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev if (((TextView) child).getText() == "") { 176a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev emptyChildCount++; 177a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 178a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 179a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return emptyChildCount; 180a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 181a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 182a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private class TestDataCallback extends AsyncListUtil.DataCallback<String> { 183a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 184a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private CountDownLatch mTilesLatch; 185a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 186a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 187a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void fillData(String[] data, int startPosition, int itemCount) { 188a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev assertTrue("Unexpected tile load @" + startPosition, mTilesLatch.getCount() > 0); 189a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev try { 190a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev Thread.sleep(100); 191a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } catch (InterruptedException e) { 192a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 193a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev for (int i = 0; i < itemCount; i++) { 194a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev data[i] = "Item #" + (startPosition + i); 195a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 196a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mTilesLatch.countDown(); 197a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 198a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 199a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 200a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public int refreshData() { 201a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return ITEM_COUNT; 202a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 203a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 204a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private void expectTiles(int count) { 205a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mTilesLatch = new CountDownLatch(count); 206a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 207a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 208a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void expectTilesInRange(int rangeStart, int rangeSize) { 209a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev expectTiles(getTileCount(rangeStart - mStartPrefetch, 210a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev rangeSize + mStartPrefetch + mEndPrefetch)); 211a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 212a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 213a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void expectNoNewTilesLoaded() { 214a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev expectTiles(0); 215a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 216a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 217a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public boolean waitForTiles(long timeoutInSeconds) throws InterruptedException { 218a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return mTilesLatch.await(timeoutInSeconds, TimeUnit.SECONDS); 219a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 220a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 221a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 222a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private class TestViewCallback extends AsyncListUtil.ViewCallback { 223a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 224a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void getItemRangeInto(int[] outRange) { 225a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev outRange[0] = mLayoutManager.findFirstVisibleItemPosition(); 226a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev outRange[1] = mLayoutManager.findLastVisibleItemPosition(); 227a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 228a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 229a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 230a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void extendRangeInto(int[] range, int[] outRange, int scrollHint) { 231a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev outRange[0] = range[0] - mStartPrefetch; 232a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev outRange[1] = range[1] + mEndPrefetch; 233a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 234a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 235a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 236a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void onDataRefresh() { 237a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mRecyclerView.getAdapter().notifyDataSetChanged(); 238a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 239a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 240a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 241a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void onItemLoaded(int position) { 242a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mRecyclerView.getAdapter().notifyItemChanged(position); 243a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 244a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 245a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 246a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private static class SimpleViewHolder extends RecyclerView.ViewHolder { 247a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 248a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public SimpleViewHolder(Context context) { 249a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev super(new TextView(context)); 250a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 251a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 252a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 253a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private class AsyncTestAdapter extends RecyclerView.Adapter<SimpleViewHolder> { 254a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 255a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private BitSet mLoadedPositions; 256a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private BitSet mExpectedPositions; 257a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 258a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private CountDownLatch mItemsLatch; 259a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public AsyncTestAdapter() { 260a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLoadedPositions = new BitSet(ITEM_COUNT); 261a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 262a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 263a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 264a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public SimpleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 265a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return new SimpleViewHolder(parent.getContext()); 266a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 267a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 268a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 269a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void onBindViewHolder(SimpleViewHolder holder, int position) { 270a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev final String item = mAsyncListUtil == null ? null : mAsyncListUtil.getItem(position); 271a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev ((TextView) (holder.itemView)).setText(item == null ? "" : item); 272a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 273a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev if (item != null) { 274a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLoadedPositions.set(position); 275a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev if (mExpectedPositions.get(position)) { 276a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mExpectedPositions.clear(position); 277a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev if (mExpectedPositions.cardinality() == 0) { 278a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mItemsLatch.countDown(); 279a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 280a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 281a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 282a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 283a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 284a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 285a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public int getItemCount() { 286a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return ITEM_COUNT; 287a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 288a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 289a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev private void expectItemsInRange(int rangeStart, int rangeSize) { 290a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mExpectedPositions = new BitSet(rangeStart + rangeSize); 291a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev for (int i = 0; i < rangeSize; i++) { 292a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev if (!mLoadedPositions.get(rangeStart + i)) { 293a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mExpectedPositions.set(rangeStart + i); 294a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 295a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 296a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mItemsLatch = new CountDownLatch(1); 297a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev if (mExpectedPositions.cardinality() == 0) { 298a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mItemsLatch.countDown(); 299a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 300a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 301a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 302a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public boolean waitForItems(long timeoutInSeconds) throws InterruptedException { 303a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev return mItemsLatch.await(timeoutInSeconds, TimeUnit.SECONDS); 304a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 305a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 306a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 307a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev class WrappedLinearLayoutManager extends LinearLayoutManager { 308a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 309a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev CountDownLatch mLayoutLatch; 310a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 311a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { 312a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev super(context, orientation, reverseLayout); 313a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 314a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 315a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void expectLayouts(int count) { 316a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLayoutLatch = new CountDownLatch(count); 317a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 318a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 31966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar public void waitForLayout(int seconds) throws Throwable { 32066826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar mLayoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS); 32166826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar checkForMainThreadException(); 32266826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar MatcherAssert.assertThat("all layouts should complete on time", 32366826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar mLayoutLatch.getCount(), CoreMatchers.is(0L)); 32466826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar // use a runnable to ensure RV layout is finished 32566826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar getInstrumentation().runOnMainSync(new Runnable() { 32666826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar @Override 32766826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar public void run() { 32866826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar } 32966826566020afc8d11f183cf3fe443ac0a022384Yigit Boyar }); 330a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 331a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev 332a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev @Override 333a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 334a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev try { 335a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev super.onLayoutChildren(recycler, state); 336a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } catch (Throwable t) { 337a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev postExceptionToInstrumentation(t); 338a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 339a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev mLayoutLatch.countDown(); 340a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 341a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev } 342a1470623b0f7c52c9e3985012bf9daeb692d7bccVladislav Kaznacheev} 343