1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.widget; 18 19import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL; 20import static android.support.v7.widget.LinearLayoutManager.VERTICAL; 21 22import static junit.framework.Assert.assertEquals; 23import static junit.framework.Assert.assertTrue; 24 25import android.os.Build; 26import android.support.test.filters.MediumTest; 27import android.support.test.filters.SdkSuppress; 28 29import org.junit.Test; 30import org.junit.runner.RunWith; 31import org.junit.runners.Parameterized; 32 33import java.util.ArrayList; 34import java.util.Arrays; 35import java.util.List; 36import java.util.concurrent.TimeUnit; 37 38@RunWith(Parameterized.class) 39@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) 40public class StaggeredGridLayoutManagerCacheTest extends BaseStaggeredGridLayoutManagerTest { 41 42 final Config mConfig; 43 final int mDx; 44 final int mDy; 45 46 public StaggeredGridLayoutManagerCacheTest(Config config, int dx, int dy) { 47 mConfig = config; 48 mDx = dx; 49 mDy = dy; 50 } 51 52 @Parameterized.Parameters(name = "config:{0},dx:{1},dy:{2}") 53 public static List<Object[]> getParams() { 54 List<Object[]> result = new ArrayList<>(); 55 List<Config> configs = createBaseVariations(); 56 for (Config config : configs) { 57 for (int dx : new int[] {-1, 0, 1}) { 58 for (int dy : new int[] {-1, 0, 1}) { 59 result.add(new Object[]{config, dx, dy}); 60 } 61 } 62 } 63 return result; 64 } 65 66 private ArrayList<RecyclerView.ViewHolder> cachedViews() { 67 return mRecyclerView.mRecycler.mCachedViews; 68 } 69 70 private boolean cachedViewsContains(int position) { 71 // Note: can't make assumptions about order here, so just check all cached views 72 for (int i = 0; i < cachedViews().size(); i++) { 73 if (cachedViews().get(i).getAdapterPosition() == position) return true; 74 } 75 return false; 76 } 77 78 public int findFirstVisibleItemPosition() { 79 int[] positions = new int[mConfig.mSpanCount]; 80 mLayoutManager.findFirstVisibleItemPositions(positions); 81 Arrays.sort(positions); 82 return positions[0]; 83 } 84 85 public int findLastVisibleItemPosition() { 86 int[] positions = new int[mConfig.mSpanCount]; 87 mLayoutManager.findLastVisibleItemPositions(positions); 88 Arrays.sort(positions); 89 return positions[mConfig.mSpanCount - 1]; 90 } 91 92 @MediumTest 93 @Test 94 public void cacheAndPrefetch() throws Throwable { 95 final Config config = (Config) mConfig.clone(); 96 setupByConfig(config); 97 waitFirstLayout(); 98 99 mActivityRule.runOnUiThread(new Runnable() { 100 @Override 101 public void run() { 102 // pretend to have an extra 5s before next frame so prefetch won't abort early 103 ((WrappedRecyclerView)mRecyclerView).setDrawingTimeOffset(5000); 104 105 // scroll to the middle, so we can move in either direction 106 mRecyclerView.scrollToPosition(mConfig.mItemCount / 2); 107 } 108 }); 109 110 mRecyclerView.setItemViewCacheSize(0); 111 { 112 mLayoutManager.expectPrefetch(1); 113 mActivityRule.runOnUiThread(new Runnable() { 114 @Override 115 public void run() { 116 mRecyclerView.mRecycler.recycleAndClearCachedViews(); 117 mRecyclerView.mGapWorker.postFromTraversal(mRecyclerView, mDx, mDy); 118 119 // Lie about post time, so prefetch executes even if it is delayed 120 mRecyclerView.mGapWorker.mPostTimeNs += TimeUnit.SECONDS.toNanos(5); 121 } 122 }); 123 mLayoutManager.waitForPrefetch(1); 124 } 125 126 mActivityRule.runOnUiThread(new Runnable() { 127 @Override 128 public void run() { 129 // validate cache state on UI thread 130 if ((config.mOrientation == HORIZONTAL && mDx == 0) 131 || (config.mOrientation == VERTICAL && mDy == 0)) { 132 assertEquals(0, cachedViews().size()); 133 } else { 134 assertEquals(config.mSpanCount, cachedViews().size()); 135 136 boolean reverseScroll = config.mOrientation == HORIZONTAL ? mDx < 0 : mDy < 0; 137 int lastVisibleItemPosition = findLastVisibleItemPosition(); 138 int firstVisibleItemPosition = findFirstVisibleItemPosition(); 139 140 for (int i = 0; i < config.mSpanCount; i++) { 141 if (mConfig.mReverseLayout == reverseScroll) { 142 // Pos scroll on pos layout, or reverse scroll on reverse layout 143 // = toward last 144 assertTrue(cachedViewsContains(lastVisibleItemPosition + 1 + i)); 145 } else { 146 // Pos scroll on reverse layout, or reverse scroll on pos layout 147 // = toward first 148 assertTrue(cachedViewsContains(firstVisibleItemPosition - 1 - i)); 149 } 150 } 151 } 152 } 153 }); 154 } 155} 156