Fix StaggaredGridLayoutManager prefetch crash

Fixes: 33654151
Test: @SmallTests pass

StaggaredGridLayoutManager prefetching would attempt to prefetch in
the wrong direction (and pass a negative distance) if a span had no
content left.

Change-Id: Ia1d8f34436d436b72e37e0058c94c2ccdf51c6ca
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index adbe4dd..19f31f8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -2097,15 +2097,23 @@
         if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) {
             mPrefetchDistances = new int[mSpanCount];
         }
+
+        int itemPrefetchCount = 0;
         for (int i = 0; i < mSpanCount; i++) {
-            mPrefetchDistances[i] = mLayoutState.mItemDirection == LAYOUT_START
+            // compute number of pixels past the edge of the viewport that the current span extends
+            int distance = mLayoutState.mItemDirection == LAYOUT_START
                     ? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine)
                     : mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine;
+            if (distance >= 0) {
+                // span extends to the edge, so prefetch next item
+                mPrefetchDistances[itemPrefetchCount] = distance;
+                itemPrefetchCount++;
+            }
         }
-        Arrays.sort(mPrefetchDistances, 0, mSpanCount);
+        Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount);
 
         // then assign them in order to the next N views (where N = span count)
-        for (int i = 0; i < mSpanCount && mLayoutState.hasMore(state); i++) {
+        for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) {
             layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition, mPrefetchDistances[i]);
             mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
         }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
index cc39a87..7ab616f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -515,6 +515,50 @@
     }
 
     @Test
+    public void prefetchStaggeredPastBoundary() {
+        StaggeredGridLayoutManager sglm =
+                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
+        mRecyclerView.setLayoutManager(sglm);
+        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
+            @Override
+            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                return new RecyclerView.ViewHolder(new View(getContext())) {};
+            }
+
+            @Override
+            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+                holder.itemView.setMinimumWidth(100);
+                holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
+            }
+
+            @Override
+            public int getItemCount() {
+                return 2;
+            }
+        });
+
+        layout(200, 100);
+        mRecyclerView.scrollBy(0, 50);
+
+        /* Each row is 50 pixels:
+         * ------------- *
+         *___0___|___1___*
+         *   0   |   1   *
+         *_______|___1___*
+         *       |   1   *
+         */
+        assertEquals(2, mRecyclerView.getChildCount());
+        assertEquals(0, sglm.getFirstChildPosition());
+        assertEquals(1, sglm.getLastChildPosition());
+
+        // prefetch upward gets nothing
+        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
+
+        // prefetch downward gets nothing (and doesn't crash...)
+        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
+    }
+
+    @Test
     public void prefetchItemsSkipAnimations() {
         LinearLayoutManager llm = new LinearLayoutManager(getContext());
         mRecyclerView.setLayoutManager(llm);