Merge "Relayout when base inner insets change" into oc-support-26.0-dev
diff --git a/api/26.0.0-SNAPSHOT.txt b/api/26.0.0-SNAPSHOT.txt
index 5e565a1..657fba6 100644
--- a/api/26.0.0-SNAPSHOT.txt
+++ b/api/26.0.0-SNAPSHOT.txt
@@ -8290,6 +8290,14 @@
 
 }
 
+package android.support.v4.utils {
+
+  public class ObjectUtils {
+    method public static boolean objectEquals(java.lang.Object, java.lang.Object);
+  }
+
+}
+
 package android.support.v4.view {
 
   public abstract class AbsSavedState implements android.os.Parcelable {
@@ -10139,10 +10147,6 @@
     method public abstract void onTabUnselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
   }
 
-  public deprecated class ActionBarActivity extends android.support.v7.app.AppCompatActivity {
-    ctor public ActionBarActivity();
-  }
-
   public class ActionBarDrawerToggle implements android.support.v4.widget.DrawerLayout.DrawerListener {
     ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, int, int);
     ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, android.support.v7.widget.Toolbar, int, int);
@@ -12504,6 +12508,8 @@
     method public boolean didStructureChange();
     method public <T> T get(int);
     method public int getItemCount();
+    method public int getRemainingScrollHorizontal();
+    method public int getRemainingScrollVertical();
     method public int getTargetScrollPosition();
     method public boolean hasTargetScrollPosition();
     method public boolean isMeasuring();
diff --git a/compat/java/android/support/v4/view/ViewCompat.java b/compat/java/android/support/v4/view/ViewCompat.java
index f10ac1d..efbf848 100644
--- a/compat/java/android/support/v4/view/ViewCompat.java
+++ b/compat/java/android/support/v4/view/ViewCompat.java
@@ -2446,7 +2446,7 @@
     /**
      * Returns the minimum width of the view.
      *
-     * <p>Prior to API 16 this will return 0.</p>
+     * <p>Prior to API 16, this method may return 0 on some platforms.</p>
      *
      * @return the minimum width the view will try to be.
      */
@@ -2457,7 +2457,7 @@
     /**
      * Returns the minimum height of the view.
      *
-     * <p>Prior to API 16 this will return 0.</p>
+     * <p>Prior to API 16, this method may return 0 on some platforms.</p>
      *
      * @return the minimum height the view will try to be.
      */
@@ -2469,8 +2469,6 @@
      * This method returns a ViewPropertyAnimator object, which can be used to animate
      * specific properties on this View.
      *
-     * <p>Prior to API 14, this method will do nothing.</p>
-     *
      * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
      */
     public static ViewPropertyAnimatorCompat animate(View view) {
diff --git a/core-utils/java/android/support/v4/utils/ObjectUtils.java b/core-utils/java/android/support/v4/utils/ObjectUtils.java
new file mode 100644
index 0000000..cd4afc7
--- /dev/null
+++ b/core-utils/java/android/support/v4/utils/ObjectUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.utils;
+
+/**
+ * This class consists of static utility methods for operating on objects.
+ */
+public class ObjectUtils {
+    /**
+     * Returns true if the arguments are equal to each other and false otherwise.
+     * @param a an object
+     * @param b an object to be compared with <code>a</code> for equality
+     * @return <code>true</code> if the arguments are equal to each other and
+     *         <code>false</code> otherwise.
+     */
+    public static boolean objectEquals(Object a, Object b) {
+        return (a == b) || (a != null && a.equals(b));
+    }
+
+    private ObjectUtils() {
+    }
+}
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 1b0b589..89fcf7c 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -17,7 +17,7 @@
 package android.support.design.widget;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.design.widget.ViewUtils.objectEquals;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index c3d1268..100b0dc 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -17,7 +17,7 @@
 package android.support.design.widget;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.design.widget.ViewUtils.objectEquals;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 5ee3b32..e5c52b3 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -17,7 +17,7 @@
 package android.support.design.widget;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.design.widget.ViewUtils.objectEquals;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.content.Context;
 import android.content.res.Resources;
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index bd02bc3..52efdde 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -64,6 +64,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStructure;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.EditText;
@@ -121,6 +122,7 @@
 
     private final FrameLayout mInputFrame;
     EditText mEditText;
+    private CharSequence mOriginalHint;
 
     private boolean mHintEnabled;
     private CharSequence mHint;
@@ -313,6 +315,24 @@
         return mTypeface;
     }
 
+    @Override
+    public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
+        if (mOriginalHint == null || mEditText == null) {
+            super.dispatchProvideAutofillStructure(structure, flags);
+            return;
+        }
+
+        // Temporarily sets child's hint to its original value so it is properly set in the
+        // child's ViewStructure.
+        final CharSequence hint = mEditText.getHint();
+        mEditText.setHint(mOriginalHint);
+        try {
+            super.dispatchProvideAutofillStructure(structure, flags);
+        } finally {
+            mEditText.setHint(hint);
+        }
+    }
+
     private void setEditText(EditText editText) {
         // If we already have an EditText, throw an exception
         if (mEditText != null) {
@@ -364,7 +384,9 @@
 
         // If we do not have a valid hint, try and retrieve it from the EditText, if enabled
         if (mHintEnabled && TextUtils.isEmpty(mHint)) {
-            setHint(mEditText.getHint());
+            // Save the hint so it can be restored on dispatchProvideAutofillStructure();
+            mOriginalHint = mEditText.getHint();
+            setHint(mOriginalHint);
             // Clear the EditText's hint as we will display it ourselves
             mEditText.setHint(null);
         }
diff --git a/design/src/android/support/design/widget/ViewUtils.java b/design/src/android/support/design/widget/ViewUtils.java
index 1762b09..c09eac1 100644
--- a/design/src/android/support/design/widget/ViewUtils.java
+++ b/design/src/android/support/design/widget/ViewUtils.java
@@ -19,10 +19,6 @@
 import android.graphics.PorterDuff;
 
 class ViewUtils {
-    static boolean objectEquals(Object a, Object b) {
-        return (a == b) || (a != null && a.equals(b));
-    }
-
     static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
         switch (value) {
             case 3:
diff --git a/design/tests/src/android/support/design/testutils/ViewStructureImpl.java b/design/tests/src/android/support/design/testutils/ViewStructureImpl.java
new file mode 100644
index 0000000..c7369e9
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/ViewStructureImpl.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.testutils;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.view.ViewStructure;
+import android.view.ViewStructure.HtmlInfo.Builder;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+/**
+ * Simple implementation of {@link ViewStructure} that's easier to use than a Mockito mock.
+ *
+ * <p>Currently supports only {@code hint}, {@code className}, and child-related methods.
+ */
+public class ViewStructureImpl extends ViewStructure {
+
+    private CharSequence mHint;
+    private String mClassName;
+    private ViewStructureImpl[] mChildren;
+
+    @Override
+    public void setHint(CharSequence hint) {
+        mHint = hint;
+    }
+
+    // Supported methods
+    @Override
+    public CharSequence getHint() {
+        return mHint;
+    }
+
+    @Override
+    public void setChildCount(int num) {
+        mChildren = new ViewStructureImpl[num];
+    }
+
+    @Override
+    public void setClassName(String className) {
+        mClassName = className;
+    }
+
+    public String getClassName() {
+        return mClassName;
+    }
+
+    @Override
+    public int addChildCount(int num) {
+        if (mChildren == null) {
+            setChildCount(num);
+            return 0;
+        }
+        final int start = mChildren.length;
+        ViewStructureImpl[] newArray = new ViewStructureImpl[start + num];
+        System.arraycopy(mChildren, 0, newArray, 0, start);
+        mChildren = newArray;
+        return start;
+    }
+
+    @Override
+    public int getChildCount() {
+        if (mChildren == null) {
+            return 0;
+        }
+        return mChildren.length;
+    }
+
+    public ViewStructureImpl getChildAt(int index) {
+        return mChildren[index];
+    }
+
+    @Override
+    public ViewStructure newChild(int index) {
+        final ViewStructureImpl child = new ViewStructureImpl();
+        mChildren[index] = child;
+        return child;
+    }
+
+    @Override
+    public ViewStructure asyncNewChild(int index) {
+        return newChild(index);
+    }
+
+
+    // Unsupported methods
+    @Override
+    public void setId(int id, String packageName, String typeName, String entryName) {
+    }
+
+    @Override
+    public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
+    }
+
+    @Override
+    public void setTransformation(Matrix matrix) {
+    }
+
+    @Override
+    public void setElevation(float elevation) {
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+    }
+
+    public void setAssistBlocked(boolean state) {
+    }
+
+    @Override
+    public void setEnabled(boolean state) {
+    }
+
+    @Override
+    public void setClickable(boolean state) {
+    }
+
+    @Override
+    public void setLongClickable(boolean state) {
+    }
+
+    @Override
+    public void setContextClickable(boolean state) {
+    }
+
+    @Override
+    public void setFocusable(boolean state) {
+    }
+
+    @Override
+    public void setFocused(boolean state) {
+    }
+
+    @Override
+    public void setAccessibilityFocused(boolean state) {
+    }
+
+    @Override
+    public void setCheckable(boolean state) {
+    }
+
+    @Override
+    public void setChecked(boolean state) {
+    }
+
+    @Override
+    public void setSelected(boolean state) {
+    }
+
+    @Override
+    public void setActivated(boolean state) {
+    }
+
+    @Override
+    public void setOpaque(boolean opaque) {
+    }
+
+    @Override
+    public void setContentDescription(CharSequence contentDescription) {
+    }
+
+    @Override
+    public void setText(CharSequence text) {
+    }
+
+    @Override
+    public void setText(CharSequence text, int selectionStart, int selectionEnd) {
+    }
+
+    @Override
+    public void setTextStyle(float size, int fgColor, int bgColor, int style) {
+    }
+
+    @Override
+    public void setTextLines(int[] charOffsets, int[] baselines) {
+    }
+
+    @Override
+    public CharSequence getText() {
+        return null;
+    }
+
+    @Override
+    public int getTextSelectionStart() {
+        return 0;
+    }
+
+    @Override
+    public int getTextSelectionEnd() {
+        return 0;
+    }
+
+    @Override
+    public Bundle getExtras() {
+        return null;
+    }
+
+    @Override
+    public boolean hasExtras() {
+        return false;
+    }
+
+    @Override
+    public AutofillId getAutofillId() {
+        return null;
+    }
+
+    @Override
+    public void setAutofillId(AutofillId id) {
+    }
+
+    public void setAutofillId(ViewStructure parent, int virtualId) {
+    }
+
+    @Override
+    public void setAutofillId(AutofillId parentId, int virtualId) {
+    }
+
+    @Override
+    public void setAutofillType(int type) {
+    }
+
+    @Override
+    public void setAutofillHints(String[] hint) {
+    }
+
+    @Override
+    public void setAutofillValue(AutofillValue value) {
+    }
+
+    @Override
+    public void setAutofillOptions(CharSequence[] options) {
+    }
+
+    @Override
+    public void setInputType(int inputType) {
+    }
+
+    @Override
+    public void setDataIsSensitive(boolean sensitive) {
+    }
+
+    @Override
+    public void asyncCommit() {
+    }
+
+    public Rect getTempRect() {
+        return null;
+    }
+
+    @Override
+    public void setWebDomain(String domain) {
+    }
+
+    @Override
+    public void setLocaleList(LocaleList localeList) {
+    }
+
+    @Override
+    public Builder newHtmlInfoBuilder(String tagName) {
+        return null;
+    }
+
+    @Override
+    public void setHtmlInfo(HtmlInfo htmlInfo) {
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
index 929fcd7..226463c 100755
--- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -49,6 +49,7 @@
 import static org.hamcrest.Matchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
@@ -58,13 +59,16 @@
 import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.Parcelable;
 import android.support.design.test.R;
 import android.support.design.testutils.TestUtils;
+import android.support.design.testutils.ViewStructureImpl;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.espresso.NoMatchingViewException;
 import android.support.test.espresso.ViewAssertion;
 import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
 import android.support.v4.widget.TextViewCompat;
 import android.util.AttributeSet;
 import android.util.SparseArray;
@@ -304,6 +308,30 @@
         assertEquals(INPUT_TEXT, info.hintText);
     }
 
+    @UiThreadTest
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    public void testDispatchProvideAutofillStructure() {
+        final Activity activity = mActivityTestRule.getActivity();
+
+        final TextInputLayout layout = activity.findViewById(R.id.textinput);
+
+        final ViewStructureImpl structure = new ViewStructureImpl();
+        layout.dispatchProvideAutofillStructure(structure, 0);
+
+        assertEquals(2, structure.getChildCount()); // EditText and TextView
+
+        // Asserts the structure.
+        final ViewStructureImpl childStructure = structure.getChildAt(0);
+        assertEquals(EditText.class.getName(), childStructure.getClassName());
+        assertEquals("Hint to the user", childStructure.getHint());
+
+        // Make sure the widget's hint was restored.
+        assertEquals("Hint to the user", layout.getHint());
+        final EditText editText = activity.findViewById(R.id.textinput_edittext);
+        assertNull(editText.getHint());
+    }
+
     /**
      * Regression test for b/31663756.
      */
diff --git a/fragment/java/android/support/v4/app/FragmentManager.java b/fragment/java/android/support/v4/app/FragmentManager.java
index 9f92d30..c49b69b 100644
--- a/fragment/java/android/support/v4/app/FragmentManager.java
+++ b/fragment/java/android/support/v4/app/FragmentManager.java
@@ -652,7 +652,7 @@
 
     int mNextFragmentIndex = 0;
 
-    ArrayList<Fragment> mAdded;
+    final ArrayList<Fragment> mAdded = new ArrayList<>();
     SparseArray<Fragment> mActive;
     ArrayList<BackStackRecord> mBackStack;
     ArrayList<Fragment> mCreatedMenus;
@@ -908,7 +908,7 @@
 
     @Override
     public List<Fragment> getFragments() {
-        if (mAdded == null) {
+        if (mAdded.isEmpty()) {
             return Collections.EMPTY_LIST;
         }
         synchronized (mAdded) {
@@ -1001,15 +1001,16 @@
             }
         }
 
-        if (mAdded != null) {
-            N = mAdded.size();
-            if (N > 0) {
-                writer.print(prefix); writer.println("Added Fragments:");
-                for (int i=0; i<N; i++) {
-                    Fragment f = mAdded.get(i);
-                    writer.print(prefix); writer.print("  #"); writer.print(i);
-                            writer.print(": "); writer.println(f.toString());
-                }
+        N = mAdded.size();
+        if (N > 0) {
+            writer.print(prefix); writer.println("Added Fragments:");
+            for (int i = 0; i < N; i++) {
+                Fragment f = mAdded.get(i);
+                writer.print(prefix);
+                writer.print("  #");
+                writer.print(i);
+                writer.print(": ");
+                writer.println(f.toString());
             }
         }
 
@@ -1801,14 +1802,12 @@
             boolean loadersRunning = false;
 
             // Must add them in the proper order. mActive fragments may be out of order
-            if (mAdded != null) {
-                final int numAdded = mAdded.size();
-                for (int i = 0; i < numAdded; i++) {
-                    Fragment f = mAdded.get(i);
-                    moveFragmentToExpectedState(f);
-                    if (f.mLoaderManager != null) {
-                        loadersRunning |= f.mLoaderManager.hasRunningLoaders();
-                    }
+            final int numAdded = mAdded.size();
+            for (int i = 0; i < numAdded; i++) {
+                Fragment f = mAdded.get(i);
+                moveFragmentToExpectedState(f);
+                if (f.mLoaderManager != null) {
+                    loadersRunning |= f.mLoaderManager.hasRunningLoaders();
                 }
             }
 
@@ -1875,9 +1874,6 @@
     }
 
     public void addFragment(Fragment fragment, boolean moveToStateNow) {
-        if (mAdded == null) {
-            mAdded = new ArrayList<Fragment>();
-        }
         if (DEBUG) Log.v(TAG, "add: " + fragment);
         makeActive(fragment);
         if (!fragment.mDetached) {
@@ -1905,10 +1901,8 @@
         if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
         final boolean inactive = !fragment.isInBackStack();
         if (!fragment.mDetached || inactive) {
-            if (mAdded != null) {
-                synchronized (mAdded) {
-                    mAdded.remove(fragment);
-                }
+            synchronized (mAdded) {
+                mAdded.remove(fragment);
             }
             if (fragment.mHasMenu && fragment.mMenuVisible) {
                 mNeedMenuInvalidate = true;
@@ -1956,11 +1950,9 @@
             fragment.mDetached = true;
             if (fragment.mAdded) {
                 // We are not already in back stack, so need to remove the fragment.
-                if (mAdded != null) {
-                    if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
-                    synchronized (mAdded) {
-                        mAdded.remove(fragment);
-                    }
+                if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
+                synchronized (mAdded) {
+                    mAdded.remove(fragment);
                 }
                 if (fragment.mHasMenu && fragment.mMenuVisible) {
                     mNeedMenuInvalidate = true;
@@ -1975,9 +1967,6 @@
         if (fragment.mDetached) {
             fragment.mDetached = false;
             if (!fragment.mAdded) {
-                if (mAdded == null) {
-                    mAdded = new ArrayList<Fragment>();
-                }
                 if (mAdded.contains(fragment)) {
                     throw new IllegalStateException("Fragment already added: " + fragment);
                 }
@@ -1995,13 +1984,11 @@
 
     @Override
     public Fragment findFragmentById(int id) {
-        if (mAdded != null) {
-            // First look through added fragments.
-            for (int i=mAdded.size()-1; i>=0; i--) {
-                Fragment f = mAdded.get(i);
-                if (f != null && f.mFragmentId == id) {
-                    return f;
-                }
+        // First look through added fragments.
+        for (int i = mAdded.size() - 1; i >= 0; i--) {
+            Fragment f = mAdded.get(i);
+            if (f != null && f.mFragmentId == id) {
+                return f;
             }
         }
         if (mActive != null) {
@@ -2018,7 +2005,7 @@
 
     @Override
     public Fragment findFragmentByTag(String tag) {
-        if (mAdded != null && tag != null) {
+        if (tag != null) {
             // First look through added fragments.
             for (int i=mAdded.size()-1; i>=0; i--) {
                 Fragment f = mAdded.get(i);
@@ -2358,9 +2345,7 @@
         } else {
             mTmpAddedFragments.clear();
         }
-        if (mAdded != null) {
-            mTmpAddedFragments.addAll(mAdded);
-        }
+        mTmpAddedFragments.addAll(mAdded);
         Fragment oldPrimaryNav = getPrimaryNavigationFragment();
         for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
             final BackStackRecord record = records.get(recordNum);
@@ -2608,7 +2593,7 @@
         }
         // We want to leave the fragment in the started state
         final int state = Math.min(mCurState, Fragment.STARTED);
-        final int numAdded = mAdded == null ? 0 : mAdded.size();
+        final int numAdded = mAdded.size();
         for (int i = 0; i < numAdded; i++) {
             Fragment fragment = mAdded.get(i);
             if (fragment.mState < state) {
@@ -2972,18 +2957,18 @@
         BackStackState[] backStack = null;
 
         // Build list of currently added fragments.
-        if (mAdded != null) {
-            N = mAdded.size();
-            if (N > 0) {
-                added = new int[N];
-                for (int i=0; i<N; i++) {
-                    added[i] = mAdded.get(i).mIndex;
-                    if (added[i] < 0) {
-                        throwException(new IllegalStateException(
-                                "Failure saving state: active " + mAdded.get(i)
-                                + " has cleared index: " + added[i]));
-                    }
-                    if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
+        N = mAdded.size();
+        if (N > 0) {
+            added = new int[N];
+            for (int i = 0; i < N; i++) {
+                added[i] = mAdded.get(i).mIndex;
+                if (added[i] < 0) {
+                    throwException(new IllegalStateException(
+                            "Failure saving state: active " + mAdded.get(i)
+                            + " has cleared index: " + added[i]));
+                }
+                if (DEBUG) {
+                    Log.v(TAG, "saveAllState: adding fragment #" + i
                             + ": " + mAdded.get(i));
                 }
             }
@@ -3093,8 +3078,8 @@
         }
 
         // Build the list of currently added fragments.
+        mAdded.clear();
         if (fms.mAdded != null) {
-            mAdded = new ArrayList<Fragment>(fms.mAdded.length);
             for (int i=0; i<fms.mAdded.length; i++) {
                 Fragment f = mActive.get(fms.mAdded[i]);
                 if (f == null) {
@@ -3110,8 +3095,6 @@
                     mAdded.add(f);
                 }
             }
-        } else {
-            mAdded = null;
         }
 
         // Build the back stack.
@@ -3168,7 +3151,7 @@
     public void noteStateNotSaved() {
         mSavedNonConfig = null;
         mStateSaved = false;
-        final int addedCount = mAdded == null ? 0 : mAdded.size();
+        final int addedCount = mAdded.size();
         for (int i = 0; i < addedCount; i++) {
             Fragment fragment = mAdded.get(i);
             if (fragment != null) {
@@ -3238,9 +3221,6 @@
     }
 
     public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
-        if (mAdded == null) {
-            return;
-        }
         for (int i = mAdded.size() - 1; i >= 0; --i) {
             final android.support.v4.app.Fragment f = mAdded.get(i);
             if (f != null) {
@@ -3250,9 +3230,6 @@
     }
 
     public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        if (mAdded == null) {
-            return;
-        }
         for (int i = mAdded.size() - 1; i >= 0; --i) {
             final android.support.v4.app.Fragment f = mAdded.get(i);
             if (f != null) {
@@ -3262,23 +3239,19 @@
     }
 
     public void dispatchConfigurationChanged(Configuration newConfig) {
-        if (mAdded != null) {
-            for (int i=0; i<mAdded.size(); i++) {
-                Fragment f = mAdded.get(i);
-                if (f != null) {
-                    f.performConfigurationChanged(newConfig);
-                }
+        for (int i = 0; i < mAdded.size(); i++) {
+            Fragment f = mAdded.get(i);
+            if (f != null) {
+                f.performConfigurationChanged(newConfig);
             }
         }
     }
 
     public void dispatchLowMemory() {
-        if (mAdded != null) {
-            for (int i=0; i<mAdded.size(); i++) {
-                Fragment f = mAdded.get(i);
-                if (f != null) {
-                    f.performLowMemory();
-                }
+        for (int i = 0; i < mAdded.size(); i++) {
+            Fragment f = mAdded.get(i);
+            if (f != null) {
+                f.performLowMemory();
             }
         }
     }
@@ -3286,17 +3259,15 @@
     public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         boolean show = false;
         ArrayList<Fragment> newMenus = null;
-        if (mAdded != null) {
-            for (int i=0; i<mAdded.size(); i++) {
-                Fragment f = mAdded.get(i);
-                if (f != null) {
-                    if (f.performCreateOptionsMenu(menu, inflater)) {
-                        show = true;
-                        if (newMenus == null) {
-                            newMenus = new ArrayList<Fragment>();
-                        }
-                        newMenus.add(f);
+        for (int i = 0; i < mAdded.size(); i++) {
+            Fragment f = mAdded.get(i);
+            if (f != null) {
+                if (f.performCreateOptionsMenu(menu, inflater)) {
+                    show = true;
+                    if (newMenus == null) {
+                        newMenus = new ArrayList<Fragment>();
                     }
+                    newMenus.add(f);
                 }
             }
         }
@@ -3317,13 +3288,11 @@
 
     public boolean dispatchPrepareOptionsMenu(Menu menu) {
         boolean show = false;
-        if (mAdded != null) {
-            for (int i=0; i<mAdded.size(); i++) {
-                Fragment f = mAdded.get(i);
-                if (f != null) {
-                    if (f.performPrepareOptionsMenu(menu)) {
-                        show = true;
-                    }
+        for (int i = 0; i < mAdded.size(); i++) {
+            Fragment f = mAdded.get(i);
+            if (f != null) {
+                if (f.performPrepareOptionsMenu(menu)) {
+                    show = true;
                 }
             }
         }
@@ -3331,13 +3300,11 @@
     }
 
     public boolean dispatchOptionsItemSelected(MenuItem item) {
-        if (mAdded != null) {
-            for (int i=0; i<mAdded.size(); i++) {
-                Fragment f = mAdded.get(i);
-                if (f != null) {
-                    if (f.performOptionsItemSelected(item)) {
-                        return true;
-                    }
+        for (int i = 0; i < mAdded.size(); i++) {
+            Fragment f = mAdded.get(i);
+            if (f != null) {
+                if (f.performOptionsItemSelected(item)) {
+                    return true;
                 }
             }
         }
@@ -3345,13 +3312,11 @@
     }
 
     public boolean dispatchContextItemSelected(MenuItem item) {
-        if (mAdded != null) {
-            for (int i=0; i<mAdded.size(); i++) {
-                Fragment f = mAdded.get(i);
-                if (f != null) {
-                    if (f.performContextItemSelected(item)) {
-                        return true;
-                    }
+        for (int i = 0; i < mAdded.size(); i++) {
+            Fragment f = mAdded.get(i);
+            if (f != null) {
+                if (f.performContextItemSelected(item)) {
+                    return true;
                 }
             }
         }
@@ -3359,12 +3324,10 @@
     }
 
     public void dispatchOptionsMenuClosed(Menu menu) {
-        if (mAdded != null) {
-            for (int i=0; i<mAdded.size(); i++) {
-                Fragment f = mAdded.get(i);
-                if (f != null) {
-                    f.performOptionsMenuClosed(menu);
-                }
+        for (int i = 0; i < mAdded.size(); i++) {
+            Fragment f = mAdded.get(i);
+            if (f != null) {
+                f.performOptionsMenuClosed(menu);
             }
         }
     }
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/app/ProgressBarManagerTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
index 2daba66..519f1de 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
@@ -19,7 +19,7 @@
 
 import android.content.Context;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
+import android.support.test.filters.LargeTest;
 import android.support.v17.leanback.testutils.PollingCheck;
 import android.view.View;
 import android.view.ViewGroup;
@@ -28,7 +28,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
-@SmallTest
+@LargeTest
 public class ProgressBarManagerTest {
 
     Context mContext;
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/app/ActionBarActivity.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
deleted file mode 100644
index 1834681..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.app;
-
-/**
- * @deprecated Use {@link android.support.v7.app.AppCompatActivity} instead.
- */
-@Deprecated
-public class ActionBarActivity extends AppCompatActivity {
-}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
index 5b9f337..afc951a 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
@@ -54,6 +54,9 @@
 
     private final @NonNull AppCompatTextViewAutoSizeHelper mAutoSizeTextHelper;
 
+    private int mStyle = Typeface.NORMAL;
+    private Typeface mFontTypeface;
+
     AppCompatTextHelper(TextView view) {
         mView = view;
         mAutoSizeTextHelper = new AppCompatTextViewAutoSizeHelper(mView);
@@ -62,7 +65,6 @@
     void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
         final Context context = mView.getContext();
         final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
-        final boolean shouldLoadFonts = shouldLoadFontResources(context);
 
         // First read the TextAppearance style id
         TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
@@ -97,8 +99,6 @@
         ColorStateList textColor = null;
         ColorStateList textColorHint = null;
         ColorStateList textColorLink = null;
-        Typeface fontTypeface = null;
-        int style = Typeface.NORMAL;
 
         // First check TextAppearance's textAllCaps value
         if (ap != -1) {
@@ -107,27 +107,8 @@
                 allCapsSet = true;
                 allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
             }
-            if (shouldLoadFonts) {
-                style = a.getInt(R.styleable.TextAppearance_android_textStyle, Typeface.NORMAL);
 
-                // If we're running on < API 26, we need to load font resources manually.
-                if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
-                        || a.hasValue(R.styleable.TextAppearance_fontFamily)) {
-                    int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
-                            ? R.styleable.TextAppearance_android_fontFamily
-                            : R.styleable.TextAppearance_fontFamily;
-                    try {
-                        fontTypeface = a.getFont(fontFamilyId, style);
-                    } catch (UnsupportedOperationException | Resources.NotFoundException e) {
-                        // Expected if it is not a font resource.
-                    }
-                    if (fontTypeface == null) {
-                        // Try with String. This is done by TextView JB+, but fails in ICS
-                        String fontFamilyName = a.getString(fontFamilyId);
-                        fontTypeface = Typeface.create(fontFamilyName, style);
-                    }
-                }
-            }
+            updateTypefaceAndStyle(context, a);
             if (Build.VERSION.SDK_INT < 23) {
                 // If we're running on < API 23, the text color may contain theme references
                 // so let's re-set using our own inflater
@@ -169,26 +150,7 @@
             }
         }
 
-        if (shouldLoadFonts) {
-            // If we're running on < API 26, we need to load font resources manually.
-            if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
-                    || a.hasValue(R.styleable.TextAppearance_fontFamily)) {
-                int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
-                        ? R.styleable.TextAppearance_android_fontFamily
-                        : R.styleable.TextAppearance_fontFamily;
-                style = a.getInt(R.styleable.TextAppearance_android_textStyle, Typeface.NORMAL);
-                try {
-                    fontTypeface = a.getFont(fontFamilyId, style);
-                } catch (UnsupportedOperationException | Resources.NotFoundException e) {
-                    // Expected if it is not a font resource.
-                }
-                if (fontTypeface == null) {
-                    // Try with String. This is done by TextView JB+, but fails in ICS
-                    String fontFamilyName = a.getString(fontFamilyId);
-                    fontTypeface = Typeface.create(fontFamilyName, style);
-                }
-            }
-        }
+        updateTypefaceAndStyle(context, a);
         a.recycle();
 
         if (textColor != null) {
@@ -203,8 +165,8 @@
         if (!hasPwdTm && allCapsSet) {
             setAllCaps(allCaps);
         }
-        if (fontTypeface != null) {
-            mView.setTypeface(fontTypeface, style);
+        if (mFontTypeface != null) {
+            mView.setTypeface(mFontTypeface, mStyle);
         }
 
         mAutoSizeTextHelper.loadFromAttributes(attrs, defStyleAttr);
@@ -233,9 +195,27 @@
         }
     }
 
-    private boolean shouldLoadFontResources(Context context) {
-        // We do not load fonts on restricted contexts for security reasons.
-        return !context.isRestricted();
+    private void updateTypefaceAndStyle(Context context, TintTypedArray a) {
+        mStyle = a.getInt(R.styleable.TextAppearance_android_textStyle, mStyle);
+
+        if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
+                || a.hasValue(R.styleable.TextAppearance_fontFamily)) {
+            int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
+                    ? R.styleable.TextAppearance_android_fontFamily
+                    : R.styleable.TextAppearance_fontFamily;
+            if (!context.isRestricted()) {
+                try {
+                    mFontTypeface = a.getFont(fontFamilyId, mStyle);
+                } catch (UnsupportedOperationException | Resources.NotFoundException e) {
+                    // Expected if it is not a font resource.
+                }
+            }
+            if (mFontTypeface == null) {
+                // Try with String. This is done by TextView JB+, but fails in ICS
+                String fontFamilyName = a.getString(fontFamilyId);
+                mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+            }
+        }
     }
 
     void onSetTextAppearance(Context context, int resId) {
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
index f857a85..056f2dc 100644
--- a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
+++ b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
@@ -146,6 +146,22 @@
             android:layout_height="wrap_content"
             app:fontFamily="@font/samplexmlfont" />
 
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/textview_fontxmlresource_fontfamily_textstyle"
+            android:text="@string/sample_text1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textStyle="italic"
+            app:fontFamily="@font/samplexmlfont" />
+
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/textview_fontxmlresource_fontfamily_textstyle2"
+            android:text="@string/sample_text1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textStyle="italic"
+            android:fontFamily="@font/samplexmlfont" />
+
         <!-- This is here to test that the TextView constructor ignores references to
          non Font resource types in the fontFamily attribute.-->
         <android.support.v7.widget.AppCompatTextView
@@ -183,6 +199,20 @@
             android:layout_height="wrap_content"
             android:textAppearance="@style/TextView_FontXmlResource" />
 
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/textview_textStyleOverride"
+            android:text="@string/sample_text1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/TextView_TextStyleOverride" />
+
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/textview_textStyleDirect"
+            android:text="@string/sample_text1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textStyle="italic" />
+
     </LinearLayout>
 
 </ScrollView>
diff --git a/v7/appcompat/tests/res/values/styles.xml b/v7/appcompat/tests/res/values/styles.xml
index e6e593b..05e2fa5 100644
--- a/v7/appcompat/tests/res/values/styles.xml
+++ b/v7/appcompat/tests/res/values/styles.xml
@@ -61,4 +61,8 @@
     <style name="TextView_FontXmlResource">
         <item name="fontFamily">@font/samplexmlfont</item>
     </style>
+
+    <style name="TextView_TextStyleOverride">
+        <item name="android:textStyle">italic</item>
+    </style>
 </resources>
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
index b7a28af..a9da3f5 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
@@ -28,6 +28,7 @@
 import android.support.annotation.ColorInt;
 import android.support.annotation.ColorRes;
 import android.support.annotation.IdRes;
+import android.support.test.filters.LargeTest;
 import android.support.test.filters.SmallTest;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.content.res.ResourcesCompat;
@@ -88,11 +89,13 @@
         onView(withText(itemText)).perform(click());
     }
 
+    @LargeTest
     @Test
     public void testPopupThemingFromXmlAttribute() {
         verifySpinnerPopupTheming(R.id.view_magenta_themed_popup, R.color.test_magenta, true);
     }
 
+    @LargeTest
     @Test
     public void testUnthemedPopupRuntimeTheming() {
         final AppCompatSpinner spinner =
@@ -106,6 +109,7 @@
         verifySpinnerPopupTheming(R.id.view_unthemed_popup, R.color.test_green, false);
     }
 
+    @LargeTest
     @Test
     public void testThemedPopupRuntimeTheming() {
         final AppCompatSpinner spinner =
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
index 0d357b0..cddf5e5 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
@@ -21,6 +21,7 @@
 import static android.support.v7.testutils.TestUtilsActions.setTextAppearance;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.content.res.ColorStateList;
@@ -170,6 +171,22 @@
     }
 
     @Test
+    public void testFontResourcesXml_setInXmlFamilyNameWithTextStyle() {
+        TextView textView =
+                mContainer.findViewById(R.id.textview_fontxmlresource_fontfamily_textstyle);
+
+        assertNotEquals(Typeface.DEFAULT, textView.getTypeface());
+    }
+
+    @Test
+    public void testFontResourcesXml_setInXmlFamilyNameWithTextStyle2() {
+        TextView textView =
+                mContainer.findViewById(R.id.textview_fontxmlresource_fontfamily_textstyle2);
+
+        assertNotEquals(Typeface.DEFAULT, textView.getTypeface());
+    }
+
+    @Test
     public void testFontResources_setInXmlStyle() {
         TextView textView = mContainer.findViewById(R.id.textview_fontresource_style);
         Typeface expected = ResourcesCompat.getFont(mActivity, R.font.samplefont);
@@ -200,4 +217,19 @@
 
         assertEquals(expected, textView.getTypeface());
     }
+
+    @Test
+    public void testTextStyle_setTextStyleInStyle() {
+        // TextView has a TextAppearance by default, but the textStyle can be overriden in style.
+        TextView textView = mContainer.findViewById(R.id.textview_textStyleOverride);
+
+        assertEquals(Typeface.ITALIC, textView.getTypeface().getStyle());
+    }
+
+    @Test
+    public void testTextStyle_setTextStyleDirectly() {
+        TextView textView = mContainer.findViewById(R.id.textview_textStyleDirect);
+
+        assertEquals(Typeface.ITALIC, textView.getTypeface().getStyle());
+    }
 }
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 3d33d24..a7bceec 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -20,6 +20,7 @@
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE;
 import static android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.app.PendingIntent;
 import android.content.ContentResolver;
@@ -72,6 +73,7 @@
 import android.widget.RelativeLayout;
 import android.widget.SeekBar;
 import android.widget.TextView;
+
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -1438,7 +1440,8 @@
         @Override
         protected void onPostExecute(Bitmap art) {
             mFetchArtTask = null;
-            if (mArtIconBitmap != mIconBitmap || mArtIconUri != mIconUri) {
+            if (!objectEquals(mArtIconBitmap, mIconBitmap)
+                    || !objectEquals(mArtIconUri, mIconUri)) {
                 mArtIconBitmap = mIconBitmap;
                 mArtIconLoadedBitmap = art;
                 mArtIconUri = mIconUri;
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
index eee05cc..5467c40 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
@@ -17,6 +17,7 @@
 package android.support.v7.media;
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -150,8 +151,7 @@
     public final void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
         MediaRouter.checkCallingThread();
 
-        if (mDiscoveryRequest == request
-                || (mDiscoveryRequest != null && mDiscoveryRequest.equals(request))) {
+        if (objectEquals(mDiscoveryRequest, request)) {
             return;
         }
 
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index 9ddde89..0d9c396 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.media;
 
+import static android.support.v4.utils.ObjectUtils.objectEquals;
 import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
 import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_LIBRARY_GROUP;
 import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_UNSELECT_REASON;
@@ -463,9 +464,7 @@
         if (selectorBuilder != null) {
             composite = new MediaRouteDiscoveryRequest(selectorBuilder.build(), activeScan);
         }
-        if (mCompositeDiscoveryRequest != composite
-                && (mCompositeDiscoveryRequest == null
-                        || !mCompositeDiscoveryRequest.equals(composite))) {
+        if (!objectEquals(mCompositeDiscoveryRequest, composite)) {
             mCompositeDiscoveryRequest = composite;
             mProvider.setDiscoveryRequest(composite);
             return true;
@@ -615,8 +614,7 @@
         }
 
         public boolean setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
-            if (mDiscoveryRequest != request
-                    && (mDiscoveryRequest == null || !mDiscoveryRequest.equals(request))) {
+            if (!objectEquals(mDiscoveryRequest, request)) {
                 mDiscoveryRequest = request;
                 return updateCompositeDiscoveryRequest();
             }
diff --git a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
index 8e09889..045b69b 100644
--- a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
+++ b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
@@ -15,6 +15,8 @@
  */
 package android.support.v7.media;
 
+import static android.support.v4.utils.ObjectUtils.objectEquals;
+
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -202,8 +204,7 @@
      * @param sessionId The new session id, or null if none.
      */
     public void setSessionId(String sessionId) {
-        if (mSessionId != sessionId
-                && (mSessionId == null || !mSessionId.equals(sessionId))) {
+        if (!objectEquals(mSessionId, sessionId)) {
             if (DEBUG) {
                 Log.d(TAG, "Session id is now: " + sessionId);
             }
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index cb57d08..8d2d7bd 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -1738,6 +1738,7 @@
             eatRequestLayout();
             onEnterLayoutOrScroll();
             TraceCompat.beginSection(TRACE_SCROLL_TAG);
+            fillRemainingScrollValues(mState);
             if (x != 0) {
                 consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                 unconsumedX = x - consumedX;
@@ -3548,6 +3549,17 @@
         return lastKnownId;
     }
 
+    final void fillRemainingScrollValues(State state) {
+        if (getScrollState() == SCROLL_STATE_SETTLING) {
+            final OverScroller scroller = mViewFlinger.mScroller;
+            state.mRemainingScrollHorizontal = scroller.getFinalX() - scroller.getCurrX();
+            state.mRemainingScrollVertical = scroller.getFinalY() - scroller.getCurrY();
+        } else {
+            state.mRemainingScrollHorizontal = 0;
+            state.mRemainingScrollVertical = 0;
+        }
+    }
+
     /**
      * The first step of a layout where we;
      * - process adapter updates
@@ -3557,6 +3569,7 @@
      */
     private void dispatchLayoutStep1() {
         mState.assertLayoutStep(State.STEP_START);
+        fillRemainingScrollValues(mState);
         mState.mIsMeasuring = false;
         eatRequestLayout();
         mViewInfoStore.clear();
@@ -4793,6 +4806,7 @@
                     eatRequestLayout();
                     onEnterLayoutOrScroll();
                     TraceCompat.beginSection(TRACE_SCROLL_TAG);
+                    fillRemainingScrollValues(mState);
                     if (dx != 0) {
                         hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
                         overscrollX = dx - hresult;
@@ -11584,6 +11598,7 @@
             }
         };
     }
+
     /**
      * <p>Contains useful information about the current RecyclerView state like target scroll
      * position or view focus. State object can also keep arbitrary data, identified by resource
@@ -11673,6 +11688,9 @@
         // that one instead
         int mFocusedSubChildId;
 
+        int mRemainingScrollHorizontal;
+        int mRemainingScrollVertical;
+
         ////////////////////////////////////////////////////////////////////////////////////////////
 
         State reset() {
@@ -11850,6 +11868,28 @@
                     : mItemCount;
         }
 
+        /**
+         * Returns remaining horizontal scroll distance of an ongoing scroll animation(fling/
+         * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is
+         * other than {@link #SCROLL_STATE_SETTLING}.
+         *
+         * @return Remaining horizontal scroll distance
+         */
+        public int getRemainingScrollHorizontal() {
+            return mRemainingScrollHorizontal;
+        }
+
+        /**
+         * Returns remaining vertical scroll distance of an ongoing scroll animation(fling/
+         * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is
+         * other than {@link #SCROLL_STATE_SETTLING}.
+         *
+         * @return Remaining vertical scroll distance
+         */
+        public int getRemainingScrollVertical() {
+            return mRemainingScrollVertical;
+        }
+
         @Override
         public String toString() {
             return "State{"
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
index 1f7beac..5529ead 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
@@ -201,7 +201,12 @@
         recyclerView.setItemViewCacheSize(0); // no cache, directly goes to pool
         recyclerView.setLayoutManager(layoutManager);
         setRecyclerView(recyclerView);
-        recyclerView.setAdapter(adapter);
+         mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                recyclerView.setAdapter(adapter);
+            }
+        });
         layoutManager.waitForLayout(1);
 
         assertEquals(layoutCount, recyclerView.getChildCount());
@@ -276,7 +281,12 @@
         recyclerView.setItemViewCacheSize(0); // no cache, directly goes to pool
         recyclerView.setLayoutManager(layoutManager);
         setRecyclerView(recyclerView);
-        recyclerView.setAdapter(adapter);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                recyclerView.setAdapter(adapter);
+            }
+        });
         layoutManager.waitForLayout(1);
 
         assertEquals(firstPassLayoutCount, recyclerView.getChildCount());
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 38b5345..be687c3 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -4571,4 +4571,74 @@
         }
     }
 
+    @Test
+    public void testRemainingScrollInLayout() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final TestAdapter adapter = new TestAdapter(100);
+
+        final CountDownLatch firstScrollDone = new CountDownLatch(1);
+        final CountDownLatch scrollFinished = new CountDownLatch(1);
+        final int[] totalScrollDistance = new int[] {0};
+        recyclerView.setLayoutManager(new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                if (firstScrollDone.getCount() < 1 && scrollFinished.getCount() == 1) {
+                    try {
+                        assertTrue("layout pass has remaining scroll",
+                                state.getRemainingScrollVertical() != 0);
+                        assertEquals("layout pass has remaining scroll",
+                                1000 - totalScrollDistance[0], state.getRemainingScrollVertical());
+                    } catch (Throwable throwable) {
+                        postExceptionToInstrumentation(throwable);
+                    }
+                }
+                super.onLayoutChildren(recycler, state);
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                firstScrollDone.countDown();
+                totalScrollDistance[0] += dy;
+                if (state.getRemainingScrollVertical() == 0) {
+                    // the last scroll pass will have remaining 0
+                    scrollFinished.countDown();
+                }
+                return super.scrollVerticallyBy(dy, recycler, state);
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+        });
+        recyclerView.setAdapter(adapter);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    setRecyclerView(recyclerView);
+                    recyclerView.smoothScrollBy(0, 1000);
+                } catch (Throwable throwable) {
+                    postExceptionToInstrumentation(throwable);
+                }
+            }
+        });
+
+        firstScrollDone.await(1, TimeUnit.SECONDS);
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    recyclerView.requestLayout();
+                } catch (Throwable throwable) {
+                    postExceptionToInstrumentation(throwable);
+                }
+            }
+        });
+        waitForIdleScroll(recyclerView);
+        assertTrue(scrollFinished.getCount() < 1);
+        assertEquals(totalScrollDistance[0], 1000);
+    }
+
 }