Merge "Ensure that the play/pause key double tap is only handled once" into oc-support-26.0-dev
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
index 0dc2cda..f9753eb 100644
--- a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -518,7 +518,7 @@
      */
     @RestrictTo(LIBRARY_GROUP)
     public float getPixelSize() {
-        if ((mVectorState == null && mVectorState.mVPathRenderer == null)
+        if (mVectorState == null || mVectorState.mVPathRenderer == null
                 || mVectorState.mVPathRenderer.mBaseWidth == 0
                 || mVectorState.mVPathRenderer.mBaseHeight == 0
                 || mVectorState.mVPathRenderer.mViewportHeight == 0
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Grid.java b/v17/leanback/src/android/support/v17/leanback/widget/Grid.java
index 6f5f366..148fef0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Grid.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Grid.java
@@ -51,7 +51,17 @@
         /**
          * Return how many items (are in the adapter).
          */
-        public abstract int getCount();
+        int getCount();
+
+        /**
+         * @return Min index to prepend, usually it's 0; but in the preLayout case,
+         * when grid was showing 5,6,7.  Removing 3,4,5 will make the layoutPosition to
+         * be 3(deleted),4,5 in prelayout pass; Grid's index is still 5,6,7 in prelayout.
+         * When we prepend in prelayout, we can call createItem(4), createItem(3), createItem(2),
+         * the minimal index is 2, which is also the delta to mapping to layoutPosition in
+         * prelayout pass.
+         */
+        int getMinIndex();
 
         /**
          * Create visible item and where the provider should measure it.
@@ -62,7 +72,7 @@
          * @param item    item[0] returns created item that will be passed in addItem() call.
          * @return length of the item.
          */
-        public abstract int createItem(int index, boolean append, Object[] item);
+        int createItem(int index, boolean append, Object[] item);
 
         /**
          * add item to given row and given edge.  The call is always after createItem().
@@ -72,26 +82,26 @@
          * @param rowIndex  Row index to put the item
          * @param edge      min_edge if not reversed or max_edge if reversed.
          */
-        public abstract void addItem(Object item, int index, int length, int rowIndex, int edge);
+        void addItem(Object item, int index, int length, int rowIndex, int edge);
 
         /**
          * Remove visible item at index.
          * @param index     0-based index of the item in provider
          */
-        public abstract void removeItem(int index);
+        void removeItem(int index);
 
         /**
          * Get edge of an existing visible item. edge will be the min_edge
          * if not reversed or the max_edge if reversed.
          * @param index     0-based index of the item in provider
          */
-        public abstract int getEdge(int index);
+        int getEdge(int index);
 
         /**
          * Get size of an existing visible item.
          * @param index     0-based index of the item in provider
          */
-        public abstract int getSize(int index);
+        int getSize(int index);
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 8149487..b9d4967 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -17,6 +17,7 @@
 import static android.support.v7.widget.RecyclerView.NO_ID;
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
 import static android.support.v7.widget.RecyclerView.VERTICAL;
 
 import android.content.Context;
@@ -275,13 +276,13 @@
         }
 
         void increasePendingMoves() {
-            if (mPendingMoves < MAX_PENDING_MOVES) {
+            if (mPendingMoves < mMaxPendingMoves) {
                 mPendingMoves++;
             }
         }
 
         void decreasePendingMoves() {
-            if (mPendingMoves > -MAX_PENDING_MOVES) {
+            if (mPendingMoves > -mMaxPendingMoves) {
                 mPendingMoves--;
             }
         }
@@ -377,7 +378,8 @@
     static final boolean TRACE = false;
 
     // maximum pending movement in one direction.
-    final static int MAX_PENDING_MOVES = 10;
+    static final int DEFAULT_MAX_PENDING_MOVES = 10;
+    int mMaxPendingMoves = DEFAULT_MAX_PENDING_MOVES;
     // minimal milliseconds to scroll window size in major direction,  we put a cap to prevent the
     // effect smooth scrolling too over to bind an item view then drag the item view back.
     final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
@@ -423,6 +425,18 @@
     private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
 
     RecyclerView.State mState;
+    // Suppose currently showing 4, 5, 6, 7; removing 2,3,4 will make the layoutPosition to be
+    // 2(deleted), 3, 4, 5 in prelayout pass. So when we add item in prelayout, we must subtract 2
+    // from index of Grid.createItem.
+    int mPositionDeltaInPreLayout;
+    // Extra layout space needs to fill in prelayout pass. Note we apply the extra space to both
+    // appends and prepends due to the fact leanback is doing mario scrolling: removing items to
+    // the left of focused item might need extra layout on the right.
+    int mExtraLayoutSpaceInPreLayout;
+    // mRetainedFirstPosInPostLayout and mRetainedLastPosInPostLayout are used in post layout pass
+    // to add those item even they are out side visible area.
+    int mRetainedFirstPosInPostLayout;
+    int mRetainedLastPosInPostLayout;
     RecyclerView.Recycler mRecycler;
 
     private static final Rect sTempRect = new Rect();
@@ -871,7 +885,7 @@
         mChildLaidOutListener = listener;
     }
 
-    private int getPositionByView(View view) {
+    private int getAdapterPositionByView(View view) {
         if (view == null) {
             return NO_POSITION;
         }
@@ -908,8 +922,8 @@
         return 0;
     }
 
-    private int getPositionByIndex(int index) {
-        return getPositionByView(getChildAt(index));
+    private int getAdapterPositionByIndex(int index) {
+        return getAdapterPositionByView(getChildAt(index));
     }
 
     void dispatchChildSelected() {
@@ -1105,6 +1119,10 @@
         }
         mRecycler = recycler;
         mState = state;
+        mPositionDeltaInPreLayout = 0;
+        mExtraLayoutSpaceInPreLayout = 0;
+        mRetainedFirstPosInPostLayout = Integer.MAX_VALUE;
+        mRetainedLastPosInPostLayout = Integer.MIN_VALUE;
     }
 
     /**
@@ -1113,6 +1131,10 @@
     private void leaveContext() {
         mRecycler = null;
         mState = null;
+        mPositionDeltaInPreLayout = 0;
+        mExtraLayoutSpaceInPreLayout = 0;
+        mRetainedFirstPosInPostLayout = Integer.MAX_VALUE;
+        mRetainedLastPosInPostLayout = Integer.MIN_VALUE;
     }
 
     /**
@@ -1122,9 +1144,6 @@
      * @return true if can fastRelayout()
      */
     private boolean layoutInit() {
-        boolean focusViewWasInTree = mGrid != null && mFocusPosition >= 0
-                && mFocusPosition >= mGrid.getFirstVisibleIndex()
-                && mFocusPosition <= mGrid.getLastVisibleIndex();
         final int newItemCount = mState.getItemCount();
         if (newItemCount == 0) {
             mFocusPosition = NO_POSITION;
@@ -1142,13 +1161,9 @@
             updateScrollController();
             updateSecondaryScrollLimits();
             mGrid.setSpacing(mSpacingPrimary);
-            if (!focusViewWasInTree && mFocusPosition != NO_POSITION) {
-                mGrid.setStart(mFocusPosition);
-            }
             return true;
         } else {
             mForceFullLayout = false;
-            int firstVisibleIndex = focusViewWasInTree ? mGrid.getFirstVisibleIndex() : 0;
 
             if (mGrid == null || mNumRows != mGrid.getNumRows()
                     || mReverseFlowPrimary != mGrid.isReversedFlow()) {
@@ -1163,14 +1178,6 @@
             mGrid.resetVisibleIndex();
             mWindowAlignment.mainAxis().invalidateScrollMin();
             mWindowAlignment.mainAxis().invalidateScrollMax();
-            if (focusViewWasInTree && firstVisibleIndex <= mFocusPosition) {
-                // if focusView was in tree, we will add item from first visible item
-                mGrid.setStart(firstVisibleIndex);
-            } else {
-                // if focusView was not in tree, it's probably because focus position jumped
-                // far away from visible range,  so use mFocusPosition as start
-                mGrid.setStart(mFocusPosition);
-            }
             return false;
         }
     }
@@ -1505,15 +1512,20 @@
     private Grid.Provider mGridProvider = new Grid.Provider() {
 
         @Override
+        public int getMinIndex() {
+            return mPositionDeltaInPreLayout;
+        }
+
+        @Override
         public int getCount() {
-            return mState.getItemCount();
+            return mState.getItemCount() + mPositionDeltaInPreLayout;
         }
 
         @Override
         public int createItem(int index, boolean append, Object[] item) {
             if (TRACE) TraceCompat.beginSection("createItem");
             if (TRACE) TraceCompat.beginSection("getview");
-            View v = getViewForPosition(index);
+            View v = getViewForPosition(index - mPositionDeltaInPreLayout);
             if (TRACE) TraceCompat.endSection();
             LayoutParams lp = (LayoutParams) v.getLayoutParams();
             RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
@@ -1610,7 +1622,7 @@
         @Override
         public void removeItem(int index) {
             if (TRACE) TraceCompat.beginSection("removeItem");
-            View v = findViewByPosition(index);
+            View v = findViewByPosition(index - mPositionDeltaInPreLayout);
             if (mInLayout) {
                 detachAndScrapView(v, mRecycler);
             } else {
@@ -1622,15 +1634,15 @@
         @Override
         public int getEdge(int index) {
             if (mReverseFlowPrimary) {
-                return getViewMax(findViewByPosition(index));
+                return getViewMax(findViewByPosition(index - mPositionDeltaInPreLayout));
             } else {
-                return getViewMin(findViewByPosition(index));
+                return getViewMin(findViewByPosition(index - mPositionDeltaInPreLayout));
             }
         }
 
         @Override
         public int getSize(int index) {
-            return getViewPrimarySize(findViewByPosition(index));
+            return getViewPrimarySize(findViewByPosition(index - mPositionDeltaInPreLayout));
         }
     };
 
@@ -1719,14 +1731,16 @@
 
     private void removeInvisibleViewsAtEnd() {
         if (mPruneChild && !mIsSlidingChildViews) {
-            mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
+            int retainedLastPos = Math.max(mRetainedLastPosInPostLayout, mFocusPosition);
+            mGrid.removeInvisibleItemsAtEnd(retainedLastPos,
                     mReverseFlowPrimary ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
         }
     }
 
     private void removeInvisibleViewsAtFront() {
         if (mPruneChild && !mIsSlidingChildViews) {
-            mGrid.removeInvisibleItemsAtFront(mFocusPosition,
+            int retainedFirstPos = Math.min(mRetainedFirstPosInPostLayout, mFocusPosition);
+            mGrid.removeInvisibleItemsAtFront(retainedFirstPos,
                     mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace: -mExtraLayoutSpace);
         }
     }
@@ -1823,13 +1837,15 @@
     }
 
     private void appendVisibleItems() {
-        mGrid.appendVisibleItems(mReverseFlowPrimary ? -mExtraLayoutSpace
-                : mSizePrimary + mExtraLayoutSpace);
+        mGrid.appendVisibleItems(mReverseFlowPrimary
+                ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
+                : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
     }
 
     private void prependVisibleItems() {
-        mGrid.prependVisibleItems(mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace
-                : -mExtraLayoutSpace);
+        mGrid.prependVisibleItems(mReverseFlowPrimary
+                ? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
+                : -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
     }
 
     /**
@@ -1843,7 +1859,7 @@
         int position = -1;
         for (int index = 0; index < childCount; index++) {
             View view = getChildAt(index);
-            position = getPositionByIndex(index);
+            position = getAdapterPositionByView(view);
             Grid.Location location = mGrid.getLocation(position);
             if (location == null) {
                 if (DEBUG) Log.w(getTag(), "fastRelayout(): no Location at " + position);
@@ -1916,7 +1932,7 @@
 
     // called by onLayoutChildren, either focus to FocusPosition or declare focusViewAvailable
     // and scroll to the view if framework focus on it.
-    private void scrollToFocusViewInLayout(boolean hadFocus, boolean alignToView) {
+    private void focusToViewInLayout(boolean hadFocus, boolean alignToView) {
         View focusView = findViewByPosition(mFocusPosition);
         if (focusView != null && alignToView) {
             scrollToView(focusView, false);
@@ -1958,6 +1974,11 @@
         }
     }
 
+    @Override
+    public boolean supportsPredictiveItemAnimations() {
+        return true;
+    }
+
     // Lays out items based on the current scroll position
     @Override
     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
@@ -1993,14 +2014,66 @@
         }
         mInLayout = true;
 
-        if (state.didStructureChange()) {
-            // didStructureChange() == true means attached item has been removed/added.
-            // scroll animation: we are unable to continue a scroll animation,
-            //    kill the scroll animation,  and let ItemAnimation move the item to new position.
-            // position smooth scroller: kill the animation and stop at final position.
-            // pending smooth scroller: stop and scroll to current focus position.
-            mBaseGridView.stopScroll();
+        saveContext(recycler, state);
+        if (state.isPreLayout()) {
+            int childCount = getChildCount();
+            if (mGrid != null && childCount > 0) {
+                int minChangedEdge = Integer.MAX_VALUE;
+                int maxChangeEdge = Integer.MIN_VALUE;
+                for (int i = 0; i < childCount; i++) {
+                    View view = getChildAt(i);
+                    LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                    if (i == 0) {
+                        // first child's layout position can be smaller than index if there were
+                        // items removed before first visible index.
+                        mPositionDeltaInPreLayout = mGrid.getFirstVisibleIndex()
+                                - lp.getViewLayoutPosition();
+                    }
+                    // if either of following happening
+                    // 1. item itself has changed or layout parameter changed
+                    // 2. item is losing focus
+                    // 3. item is gaining focus
+                    if (lp.isItemChanged() || lp.isItemRemoved() || view.isLayoutRequested()
+                            || (!view.hasFocus() && mFocusPosition == lp.getViewAdapterPosition())
+                            || (view.hasFocus() && mFocusPosition != lp.getViewAdapterPosition())) {
+                        minChangedEdge = Math.min(minChangedEdge, getViewMin(view));
+                        maxChangeEdge = Math.max(maxChangeEdge, getViewMax(view));
+                    }
+                }
+                if (maxChangeEdge > minChangedEdge) {
+                    mExtraLayoutSpaceInPreLayout = maxChangeEdge - minChangedEdge;
+                }
+                // append items for mExtraLayoutSpaceInPreLayout
+                appendVisibleItems();
+                prependVisibleItems();
+            }
+            mInLayout = false;
+            leaveContext();
+            if (DEBUG) Log.v(getTag(), "layoutChildren end");
+            return;
         }
+
+        // figure out the child positions that needs to be retained in post layout pass, e.g.
+        // When a child is pushed out, they needs to be added in post layout pass.
+        if (state.willRunPredictiveAnimations() && getChildCount() > 0) {
+            for (int i = 0; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                int pos = mBaseGridView.getChildViewHolder(child).getAdapterPosition();
+                if (pos >= 0) {
+                    mRetainedFirstPosInPostLayout = pos;
+                    break;
+                }
+            }
+            for (int i = getChildCount() - 1; i >= 0; i--) {
+                View child = getChildAt(i);
+                int pos = mBaseGridView.getChildViewHolder(child).getAdapterPosition();
+                if (pos >= 0) {
+                    mRetainedLastPosInPostLayout = pos;
+                    break;
+                }
+            }
+        }
+        // check if we need align to mFocusPosition, this is usually true unless in smoothScrolling
         final boolean scrollToFocus = !isSmoothScrolling()
                 && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED;
         if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
@@ -2008,37 +2081,35 @@
             mSubFocusPosition = 0;
         }
         mFocusPositionOffset = 0;
-        saveContext(recycler, state);
 
         View savedFocusView = findViewByPosition(mFocusPosition);
         int savedFocusPos = mFocusPosition;
         int savedSubFocusPos = mSubFocusPosition;
         boolean hadFocus = mBaseGridView.hasFocus();
-
-        // Track the old focus view so we can adjust our system scroll position
-        // so that any scroll animations happening now will remain valid.
-        // We must use same delta in Pre Layout (if prelayout exists) and second layout.
-        // So we cache the deltas in PreLayout and use it in second layout.
-        int delta = 0, deltaSecondary = 0;
-        if (mFocusPosition != NO_POSITION && scrollToFocus
-                && mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
-            // FIXME: we should get the remaining scroll animation offset from RecyclerView
-            if (savedFocusView != null) {
-                if (getScrollPosition(savedFocusView, savedFocusView.findFocus(), sTwoInts)) {
-                    delta = sTwoInts[0];
-                    deltaSecondary = sTwoInts[1];
-                }
-            }
-        }
+        final int firstVisibleIndex = mGrid != null ? mGrid.getFirstVisibleIndex() : NO_POSITION;
+        final int lastVisibleIndex = mGrid != null ? mGrid.getLastVisibleIndex() : NO_POSITION;
 
         if (mInFastRelayout = layoutInit()) {
+            // If grid view is empty, we will start from mFocusPosition
+            mGrid.setStart(mFocusPosition);
             fastRelayout();
         } else {
+            // layoutInit() has detached all views, so start from scratch
             mInLayoutSearchFocus = hadFocus;
-            if (mFocusPosition != NO_POSITION) {
-                // appends items till focus position.
-                while (appendOneColumnVisibleItems()
-                        && findViewByPosition(mFocusPosition) == null) ;
+            int startFromPosition, endPos;
+            if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
+                    || mFocusPosition < firstVisibleIndex)) {
+                startFromPosition = endPos = mFocusPosition;
+            } else {
+                startFromPosition = firstVisibleIndex;
+                endPos = lastVisibleIndex;
+            }
+
+            mGrid.setStart(startFromPosition);
+            if (endPos != NO_POSITION) {
+                while (appendOneColumnVisibleItems() && findViewByPosition(endPos) == null) {
+                    // continuously append items until endPos
+                }
             }
         }
         // multiple rounds: scrollToView of first round may drag first/last child into
@@ -2050,7 +2121,7 @@
             updateScrollLimits();
             oldFirstVisible = mGrid.getFirstVisibleIndex();
             oldLastVisible = mGrid.getLastVisibleIndex();
-            scrollToFocusViewInLayout(hadFocus, scrollToFocus);
+            focusToViewInLayout(hadFocus, scrollToFocus);
             appendVisibleItems();
             prependVisibleItems();
             removeInvisibleViewsAtFront();
@@ -2058,14 +2129,32 @@
         } while (mGrid.getFirstVisibleIndex() != oldFirstVisible
                 || mGrid.getLastVisibleIndex() != oldLastVisible);
 
-        if (scrollToFocus) {
-            scrollDirectionPrimary(-delta);
-            scrollDirectionSecondary(-deltaSecondary);
+        if (scrollToFocus && mBaseGridView.getScrollState() == SCROLL_STATE_SETTLING) {
+            // if still running scroll animation (excluding smoothscroll case), compensate for
+            // remaining distance so that the animation will continue and later stopped at
+            // perfectly aligned position.
+            final int remainingScrollX = state.getRemainingScrollHorizontal();
+            final int remainingScrollY = state.getRemainingScrollVertical();
+            if (remainingScrollX != 0 || remainingScrollY != 0) {
+                if (mOrientation == HORIZONTAL) {
+                    scrollDirectionPrimary(-remainingScrollX);
+                    scrollDirectionSecondary(-remainingScrollY);
+                } else {
+                    scrollDirectionPrimary(-remainingScrollY);
+                    scrollDirectionSecondary(-remainingScrollX);
+                }
+                appendVisibleItems();
+                prependVisibleItems();
+                removeInvisibleViewsAtFront();
+                removeInvisibleViewsAtEnd();
+            }
         }
-        appendVisibleItems();
-        prependVisibleItems();
-        removeInvisibleViewsAtFront();
-        removeInvisibleViewsAtEnd();
+        while (mGrid.getLastVisibleIndex() < mRetainedLastPosInPostLayout) {
+            appendOneColumnVisibleItems();
+        }
+        while (mGrid.getFirstVisibleIndex() > mRetainedFirstPosInPostLayout) {
+            prependOneColumnVisibleItems();
+        }
 
         if (DEBUG) {
             StringWriter sw = new StringWriter();
@@ -2090,10 +2179,10 @@
             dispatchChildSelected();
         }
         dispatchChildSelectedAndPositioned();
-
         if (mIsSlidingChildViews) {
             scrollDirectionPrimary(getSlideOutDistance());
         }
+
         mInLayout = false;
         leaveContext();
         if (DEBUG) Log.v(getTag(), "layoutChildren end");
@@ -2524,8 +2613,8 @@
             int pos = mFocusPosition + mFocusPositionOffset;
             if (positionStart <= pos) {
                 if (positionStart + itemCount > pos) {
-                    // stop updating offset after the focus item was removed
-                    mFocusPositionOffset = Integer.MIN_VALUE;
+                    // the focus item was removed
+                    mFocusPositionOffset += positionStart - pos;
                 } else {
                     mFocusPositionOffset -= itemCount;
                 }
@@ -2569,7 +2658,7 @@
         if (mFocusSearchDisabled) {
             return true;
         }
-        if (getPositionByView(child) == NO_POSITION) {
+        if (getAdapterPositionByView(child) == NO_POSITION) {
             // This is could be the last view in DISAPPEARING animation.
             return true;
         }
@@ -2637,7 +2726,7 @@
         if (mIsSlidingChildViews) {
             return;
         }
-        int newFocusPosition = getPositionByView(view);
+        int newFocusPosition = getAdapterPositionByView(view);
         int newSubFocusPosition = getSubPositionByView(view, childView);
         if (newFocusPosition != mFocusPosition || newSubFocusPosition != mSubFocusPosition) {
             mFocusPosition = newFocusPosition;
@@ -2678,7 +2767,7 @@
     }
 
     private boolean getNoneAlignedPosition(View view, int[] deltas) {
-        int pos = getPositionByView(view);
+        int pos = getAdapterPositionByView(view);
         int viewMin = getViewMin(view);
         int viewMax = getViewMax(view);
         // we either align "firstView" to left/top padding edge
@@ -2942,7 +3031,7 @@
         }
         final int focusedRow = mGrid.getLocation(pos).row;
         for (int i = getChildCount() - 1; i >= 0; i--) {
-            int position = getPositionByIndex(i);
+            int position = getAdapterPositionByIndex(i);
             Grid.Location loc = mGrid.getLocation(position);
             if (loc != null && loc.row == focusedRow) {
                 if (position < pos) {
@@ -2974,7 +3063,7 @@
             final int movement = getMovement(direction);
             final View focused = recyclerView.findFocus();
             final int focusedIndex = findImmediateChildIndex(focused);
-            final int focusedPos = getPositionByIndex(focusedIndex);
+            final int focusedPos = getAdapterPositionByIndex(focusedIndex);
             // Add focusables of focused item.
             if (focusedPos != NO_POSITION) {
                 findViewByPosition(focusedPos).addFocusables(views,  direction, focusableMode);
@@ -3013,7 +3102,7 @@
                     }
                     continue;
                 }
-                int position = getPositionByIndex(i);
+                int position = getAdapterPositionByIndex(i);
                 Grid.Location loc = mGrid.getLocation(position);
                 if (loc == null) {
                     continue;
@@ -3315,7 +3404,7 @@
         // save views currently is on screen (TODO save cached views)
         for (int i = 0, count = getChildCount(); i < count; i++) {
             View view = getChildAt(i);
-            int position = getPositionByView(view);
+            int position = getAdapterPositionByView(view);
             if (position != NO_POSITION) {
                 bundle = mChildrenStates.saveOnScreenView(bundle, view, position);
             }
@@ -3426,7 +3515,7 @@
             if (!canScrollTo(child)) {
                 continue;
             }
-            int position = getPositionByIndex(index);
+            int position = getAdapterPositionByIndex(index);
             int rowIndex = mGrid.getRowIndex(position);
             if (focusedRow == NO_POSITION) {
                 focusPosition = position;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java b/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java
index 04a384a..226bd51 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java
@@ -76,7 +76,8 @@
             return false;
         }
         boolean filledOne = false;
-        for (int index = getStartIndexForPrepend(); index >= 0; index--) {
+        int minIndex = mProvider.getMinIndex();
+        for (int index = getStartIndexForPrepend(); index >= minIndex; index--) {
             int size = mProvider.createItem(index, false, mTmpItem);
             int edge;
             if (mFirstVisibleIndex < 0 || mLastVisibleIndex < 0) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
index 7fa54e2..eb9528e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
@@ -141,8 +141,6 @@
         if (mLocations.size() == 0) {
             return false;
         }
-        final int count = mProvider.getCount();
-        final int firstIndex = getFirstIndex();
         int itemIndex;
         int edge;
         int offset;
@@ -165,7 +163,8 @@
                 return false;
             }
         }
-        for (; itemIndex >= mFirstIndex; itemIndex--) {
+        int firstIndex = Math.max(mProvider.getMinIndex(), mFirstIndex);
+        for (; itemIndex >= firstIndex; itemIndex--) {
             Location loc = getLocation(itemIndex);
             int rowIndex = loc.row;
             int size = mProvider.createItem(itemIndex, false, mTmpItem);
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
index 6f5529e..605adfa 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
@@ -33,6 +33,11 @@
         }
 
         @Override
+        public int getMinIndex() {
+            return 0;
+        }
+
+        @Override
         public int getCount() {
             return mCount;
         }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
index 4d4bede..8b38db0 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -442,16 +442,19 @@
      * To wait the ItemAnimator start, you can use waitForLayout() to make sure layout pass has
      * processed adapter change.
      */
-    protected void waitForItemAnimation(int timeoutMs) {
-        RecyclerView.ItemAnimator.ItemAnimatorFinishedListener listener = mock(
+    protected void waitForItemAnimation(int timeoutMs) throws Throwable {
+        final RecyclerView.ItemAnimator.ItemAnimatorFinishedListener listener = mock(
                 RecyclerView.ItemAnimator.ItemAnimatorFinishedListener.class);
-        if (mGridView.getItemAnimator().isRunning(listener)) {
-            verify(listener, timeout(timeoutMs).atLeastOnce())
-                    .onAnimationsFinished();
-        }
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().isRunning(listener);
+            }
+        });
+        verify(listener, timeout(timeoutMs).atLeastOnce()).onAnimationsFinished();
     }
 
-    protected void waitForItemAnimation() {
+    protected void waitForItemAnimation() throws Throwable {
         waitForItemAnimation(WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS);
     }
 
@@ -774,6 +777,241 @@
         verifyEdgesSame(endEdges, endEdges2);
     }
 
+    void preparePredictiveLayout() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.getItemAnimator().setAddDuration(1000);
+                mGridView.getItemAnimator().setRemoveDuration(1000);
+                mGridView.getItemAnimator().setMoveDuration(1000);
+                mGridView.getItemAnimator().setChangeDuration(1000);
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+    }
+
+    @Test
+    public void testPredictiveLayoutAdd1() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(51, new int[]{300, 300, 300, 300});
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation(5000);
+        assertEquals(50, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutAdd2() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.addItems(50, new int[]{300, 300, 300, 300});
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation(5000);
+        assertEquals(54, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove1() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(51, 3);
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation(5000);
+        assertEquals(50, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove2() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(46, 3);
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation(5000);
+        assertEquals(47, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove3() throws Throwable {
+        preparePredictiveLayout();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(0, 51);
+            }
+        });
+        waitForItemAnimationStart();
+        waitForItemAnimation(5000);
+        assertEquals(0, mGridView.getSelectedPosition());
+        assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove4() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        waitForScrollIdle();
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(0, 49);
+            }
+        });
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testPredictiveLayoutRemove5() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 3;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        waitForScrollIdle();
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(50, 40);
+            }
+        });
+        assertEquals(50, mGridView.getSelectedPosition());
+        scrollToBegin(mVerifyLayout);
+        verifyBeginAligned();
+    }
+
+    @Test
+    public void testContinuousSwapForward() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(150);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        for (int i = 150; i < 199; i++) {
+            final int swapIndex = i;
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mActivity.swap(swapIndex, swapIndex + 1);
+                }
+            });
+            Thread.sleep(10);
+        }
+        waitForItemAnimation();
+        assertEquals(199, mGridView.getSelectedPosition());
+        // check if ItemAnimation finishes at aligned positions:
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(199).getLeft();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(199).getLeft());
+    }
+
+    @Test
+    public void testContinuousSwapBackward() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(50);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        for (int i = 50; i > 0; i--) {
+            final int swapIndex = i;
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mActivity.swap(swapIndex, swapIndex - 1);
+                }
+            });
+            Thread.sleep(10);
+        }
+        waitForItemAnimation();
+        assertEquals(0, mGridView.getSelectedPosition());
+        // check if ItemAnimation finishes at aligned positions:
+        int leftEdge = mGridView.getLayoutManager().findViewByPosition(0).getLeft();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(0).getLeft());
+    }
+
     @Test
     public void testItemMovedHorizontal() throws Throwable {
         Intent intent = new Intent();
@@ -784,7 +1022,12 @@
         mOrientation = BaseGridView.HORIZONTAL;
         mNumRows = 3;
 
-        mGridView.setSelectedPositionSmooth(150);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(150);
+            }
+        });
         waitForScrollIdle(mVerifyLayout);
         performAndWaitForAnimation(new Runnable() {
             @Override
@@ -1216,13 +1459,24 @@
             }
         });
 
-        performAndWaitForAnimation(new Runnable() {
+        mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 int[] newItems = new int[]{300, 300, 300};
                 mActivity.addItems(0, newItems);
             }
         });
+        waitForScrollIdle();
+        int topEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.requestLayout();
+            }
+        });
+        waitForScrollIdle();
+        assertEquals(topEdge,
+                mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop());
     }
 
     @Test
@@ -1274,12 +1528,12 @@
         Intent intent = new Intent();
         intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
                 R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
         initActivity(intent);
         mOrientation = BaseGridView.HORIZONTAL;
         mNumRows = 1;
 
-        final int focusToIndex = 40;
+        final int focusToIndex = 200;
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -1316,12 +1570,12 @@
         Intent intent = new Intent();
         intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
                 R.layout.horizontal_linear);
-        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
         initActivity(intent);
         mOrientation = BaseGridView.HORIZONTAL;
         mNumRows = 1;
 
-        final int focusToIndex = 40;
+        final int focusToIndex = 200;
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -1340,9 +1594,10 @@
         });
         waitForLayout();
 
-        assertFalse("removing the index of attached child should kill smooth scroller",
+        assertTrue("removing the index of attached child should not kill smooth scroller",
                 mGridView.getLayoutManager().isSmoothScrolling());
         waitForItemAnimation();
+        waitForScrollIdle();
         int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
 
         mActivityTestRule.runOnUiThread(new Runnable() {
@@ -1378,7 +1633,8 @@
         assertTrue(mGridView.getChildAt(0).hasFocus());
 
         // Pressing lots of key to make sure smooth scroller is running
-        for (int i = 0; i < 20; i++) {
+        mGridView.mLayoutManager.mMaxPendingMoves = 100;
+        for (int i = 0; i < 100; i++) {
             sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
         }
 
@@ -1394,10 +1650,11 @@
         });
         waitForLayout();
 
-        assertFalse("removing the index of attached child should kill smooth scroller",
+        assertTrue("removing the index of attached child should not kill smooth scroller",
                 mGridView.getLayoutManager().isSmoothScrolling());
 
         waitForItemAnimation();
+        waitForScrollIdle();
         int focusIndex = mGridView.getSelectedPosition();
         int topEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop();
 
@@ -2349,7 +2606,8 @@
                 mGridView.stopScroll();
             }
         });
-        Thread.sleep(100);
+        waitForScrollIdle();
+        waitForItemAnimation();
         assertEquals(mGridView.getSelectedPosition(), targetPosition);
         assertSame(mGridView.getLayoutManager().findViewByPosition(targetPosition),
                 mGridView.findFocus());
@@ -3797,7 +4055,7 @@
 
         initActivity(intent);
 
-        final HashSet<View> removeAnimationFinishedViews = new HashSet();
+        final HashSet<View> moveAnimationViews = new HashSet();
         mActivity.mImportantForAccessibilityListener =
                 new GridActivity.ImportantForAccessibilityListener() {
             RecyclerView.LayoutManager mLM = mGridView.getLayoutManager();
@@ -3805,7 +4063,7 @@
             public void onImportantForAccessibilityChanged(View view, int newValue) {
                 // simulates talkack, having setImportantForAccessibility to call
                 // onInitializeAccessibilityNodeInfoForItem() for the DISAPPEARING items.
-                if (removeAnimationFinishedViews.contains(view)) {
+                if (moveAnimationViews.contains(view)) {
                     AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
                     mLM.onInitializeAccessibilityNodeInfoForItem(
                             null, null, view, info);
@@ -3814,19 +4072,23 @@
         };
         final int lastPos = mGridView.getChildAdapterPosition(
                 mGridView.getChildAt(mGridView.getChildCount() - 1));
+        final int numItemsToPushOut = mNumRows;
+        for (int i = 0; i < numItemsToPushOut; i++) {
+            moveAnimationViews.add(
+                    mGridView.getChildAt(mGridView.getChildCount() - 1 - i));
+        }
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 mGridView.setItemAnimator(new DefaultItemAnimator() {
                     @Override
-                    public void onRemoveFinished(RecyclerView.ViewHolder item) {
-                        removeAnimationFinishedViews.add(item.itemView);
+                    public void onMoveFinished(RecyclerView.ViewHolder item) {
+                        moveAnimationViews.remove(item.itemView);
                     }
                 });
                 mGridView.getLayoutManager().setItemPrefetchEnabled(false);
             }
         });
-        final int numItemsToPushOut = mNumRows;
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -3838,7 +4100,7 @@
                 mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems);
             }
         });
-        while (removeAnimationFinishedViews.size() != numItemsToPushOut) {
+        while (moveAnimationViews.size() != 0) {
             Thread.sleep(100);
         }
     }
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionBarOverlayLayout.java b/v7/appcompat/src/android/support/v7/widget/ActionBarOverlayLayout.java
index dff9fed..b5cdc7a 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionBarOverlayLayout.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionBarOverlayLayout.java
@@ -80,6 +80,7 @@
     private final Rect mLastBaseContentInsets = new Rect();
     private final Rect mContentInsets = new Rect();
     private final Rect mBaseInnerInsets = new Rect();
+    private final Rect mLastBaseInnerInsets = new Rect();
     private final Rect mInnerInsets = new Rect();
     private final Rect mLastInnerInsets = new Rect();
 
@@ -293,6 +294,10 @@
 
         mBaseInnerInsets.set(systemInsets);
         ViewUtils.computeFitSystemWindows(this, mBaseInnerInsets, mBaseContentInsets);
+        if (!mLastBaseInnerInsets.equals(mBaseInnerInsets)) {
+            changed = true;
+            mLastBaseInnerInsets.set(mBaseInnerInsets);
+        }
         if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
             changed = true;
             mLastBaseContentInsets.set(mBaseContentInsets);