Merge "Support custom views on snackbars"
diff --git a/annotations/AndroidManifest.xml b/annotations/AndroidManifest.xml
index 6f24ecb..2593e0e 100644
--- a/annotations/AndroidManifest.xml
+++ b/annotations/AndroidManifest.xml
@@ -1,2 +1,2 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest package="android.support.annotations" />
+<manifest package="android.support.annotation" />
diff --git a/api/current.txt b/api/current.txt
index 23e790c..ccc0abd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9201,6 +9201,21 @@
     method public void setDividerAllowedBelow(boolean);
   }
 
+  public class SeekBarPreference extends android.support.v7.preference.Preference {
+    ctor public SeekBarPreference(android.content.Context, android.util.AttributeSet, int, int);
+    ctor public SeekBarPreference(android.content.Context, android.util.AttributeSet, int);
+    ctor public SeekBarPreference(android.content.Context, android.util.AttributeSet);
+    ctor public SeekBarPreference(android.content.Context);
+    method public int getMax();
+    method public int getMin();
+    method public int getValue();
+    method public boolean isAdjustable();
+    method public void setAdjustable(boolean);
+    method public void setMax(int);
+    method public void setMin(int);
+    method public void setValue(int);
+  }
+
   public class SwitchPreferenceCompat extends android.support.v7.preference.TwoStatePreference {
     ctor public SwitchPreferenceCompat(android.content.Context, android.util.AttributeSet, int, int);
     ctor public SwitchPreferenceCompat(android.content.Context, android.util.AttributeSet, int);
diff --git a/design/src/android/support/design/internal/BottomNavigationMenuView.java b/design/src/android/support/design/internal/BottomNavigationMenuView.java
index 158dda7..df361e6 100644
--- a/design/src/android/support/design/internal/BottomNavigationMenuView.java
+++ b/design/src/android/support/design/internal/BottomNavigationMenuView.java
@@ -16,6 +16,8 @@
 
 package android.support.design.internal;
 
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -32,8 +34,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
 /**
  * @hide For internal use only.
  */
@@ -55,6 +55,7 @@
     private ColorStateList mItemIconTint;
     private ColorStateList mItemTextColor;
     private int mItemBackgroundRes;
+    private int[] mTempChildWidths;
 
     private BottomNavigationPresenter mPresenter;
     private MenuBuilder mMenu;
@@ -89,6 +90,7 @@
                 mMenu.performItemAction(itemView.getItemData(), mPresenter, 0);
             }
         };
+        mTempChildWidths = new int[BottomNavigationMenu.MAX_ITEM_COUNT];
     }
 
     @Override
@@ -105,10 +107,8 @@
         final int width = MeasureSpec.getSize(widthMeasureSpec);
         final int count = getChildCount();
 
-        final int childState = 0;
         final int heightSpec = MeasureSpec.makeMeasureSpec(mItemHeight, MeasureSpec.EXACTLY);
 
-        final int[] childWidths = new int[count];
         if (mShiftingMode) {
             final int inactiveCount = count - 1;
             final int activeMaxAvailable = width - inactiveCount * mInactiveItemMinWidth;
@@ -117,9 +117,9 @@
             final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth);
             int extra = width - activeWidth - inactiveWidth * inactiveCount;
             for (int i = 0; i < count; i++) {
-                childWidths[i] = (i == mActiveButton) ? activeWidth : inactiveWidth;
+                mTempChildWidths[i] = (i == mActiveButton) ? activeWidth : inactiveWidth;
                 if (extra > 0) {
-                    childWidths[i]++;
+                    mTempChildWidths[i]++;
                     extra--;
                 }
             }
@@ -128,9 +128,9 @@
             final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth);
             int extra = width - childWidth * count;
             for (int i = 0; i < count; i++) {
-                childWidths[i] = childWidth;
+                mTempChildWidths[i] = childWidth;
                 if (extra > 0) {
-                    childWidths[i]++;
+                    mTempChildWidths[i]++;
                     extra--;
                 }
             }
@@ -142,7 +142,7 @@
             if (child.getVisibility() == GONE) {
                 continue;
             }
-            child.measure(MeasureSpec.makeMeasureSpec(childWidths[i], MeasureSpec.EXACTLY),
+            child.measure(MeasureSpec.makeMeasureSpec(mTempChildWidths[i], MeasureSpec.EXACTLY),
                     heightSpec);
             ViewGroup.LayoutParams params = child.getLayoutParams();
             params.width = child.getMeasuredWidth();
@@ -150,9 +150,8 @@
         }
         setMeasuredDimension(
                 ViewCompat.resolveSizeAndState(totalWidth,
-                        MeasureSpec.makeMeasureSpec(totalWidth, MeasureSpec.EXACTLY), childState),
-                ViewCompat.resolveSizeAndState(mItemHeight, heightSpec,
-                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+                        MeasureSpec.makeMeasureSpec(totalWidth, MeasureSpec.EXACTLY), 0),
+                ViewCompat.resolveSizeAndState(mItemHeight, heightSpec, 0));
     }
 
     @Override
@@ -180,19 +179,34 @@
         return 0;
     }
 
-    public void setIconTintList(ColorStateList color) {
-        mItemIconTint = color;
+    /**
+     * Set the tint which is applied to the menu items' icons.
+     *
+     * @param tint the tint to apply.
+     */
+    public void setIconTintList(ColorStateList tint) {
+        mItemIconTint = tint;
         if (mButtons == null) return;
         for (BottomNavigationItemView item : mButtons) {
-            item.setIconTintList(color);
+            item.setIconTintList(tint);
         }
     }
 
+    /**
+     * Returns the tint which is applied to menu items' icons.
+     *
+     * @return The ColorStateList that is used to tint menu items' icons.
+     */
     @Nullable
     public ColorStateList getIconTintList() {
         return mItemIconTint;
     }
 
+    /**
+     * Set the text color to be used on menu items.
+     *
+     * @param color the ColorStateList used for menu items' text.
+     */
     public void setItemTextColor(ColorStateList color) {
         mItemTextColor = color;
         if (mButtons == null) return;
@@ -201,10 +215,19 @@
         }
     }
 
+    /**
+     * Returns the text color used on menu items.
+     *
+     * @return the ColorStateList used for menu items' text.
+     */
     public ColorStateList getItemTextColor() {
         return mItemTextColor;
     }
 
+    /**
+     * Sets the resource id to be used for item background.
+     * @param background the resource id of the background.
+     */
     public void setItemBackgroundRes(int background) {
         mItemBackgroundRes = background;
         if (mButtons == null) return;
@@ -213,6 +236,11 @@
         }
     }
 
+    /**
+     * Returns the background resource of the menu items.
+     *
+     * @return the resource id of the background.
+     */
     public int getItemBackgroundRes() {
         return mItemBackgroundRes;
     }
diff --git a/design/src/android/support/design/widget/BottomNavigationView.java b/design/src/android/support/design/widget/BottomNavigationView.java
index 476889f..1d36aa8 100644
--- a/design/src/android/support/design/widget/BottomNavigationView.java
+++ b/design/src/android/support/design/widget/BottomNavigationView.java
@@ -210,7 +210,7 @@
     }
 
     /**
-     * Returns the tint which is applied to menu items' icons.
+     * Returns the text color used on menu items.
      *
      * @see #setItemTextColor(ColorStateList)
      *
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 3355956..c97d6e1 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -58,6 +58,7 @@
 import android.text.method.PasswordTransformationMethod;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -159,6 +160,8 @@
     private boolean mHasReconstructedEditTextBackground;
     private boolean mInDrawableStateChanged;
 
+    private boolean mRestoringSavedState;
+
     public TextInputLayout(Context context) {
         this(context, null);
     }
@@ -316,7 +319,7 @@
         mEditText.addTextChangedListener(new TextWatcher() {
             @Override
             public void afterTextChanged(Editable s) {
-                updateLabelState(true);
+                updateLabelState(!mRestoringSavedState);
                 if (mCounterEnabled) {
                     updateCounter(s.length());
                 }
@@ -398,10 +401,14 @@
 
         if (hasText || (isEnabled() && (isFocused || isErrorShowing))) {
             // We should be showing the label so do so if it isn't already
-            collapseHint(animate);
+            if (mHintExpanded) {
+                collapseHint(animate);
+            }
         } else {
             // We should not be showing the label so hide it
-            expandHint(animate);
+            if (!mHintExpanded) {
+                expandHint(animate);
+            }
         }
     }
 
@@ -797,8 +804,8 @@
         } else {
             mCounterOverflowed = length > mCounterMaxLength;
             if (wasCounterOverflowed != mCounterOverflowed) {
-                TextViewCompat.setTextAppearance(mCounterView, mCounterOverflowed ?
-                        mCounterOverflowTextAppearance : mCounterTextAppearance);
+                TextViewCompat.setTextAppearance(mCounterView, mCounterOverflowed
+                        ? mCounterOverflowTextAppearance : mCounterTextAppearance);
             }
             mCounterView.setText(getContext().getString(R.string.character_counter_pattern,
                     length, mCounterMaxLength));
@@ -887,7 +894,7 @@
             super(superState);
         }
 
-        public SavedState(Parcel source, ClassLoader loader) {
+        SavedState(Parcel source, ClassLoader loader) {
             super(source, loader);
             error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
 
@@ -942,6 +949,13 @@
         requestLayout();
     }
 
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        mRestoringSavedState = true;
+        super.dispatchRestoreInstanceState(container);
+        mRestoringSavedState = false;
+    }
+
     /**
      * Returns the error message that was set to be displayed with
      * {@link #setError(CharSequence)}, or <code>null</code> if no error was set
@@ -1211,24 +1225,24 @@
         applyPasswordToggleTint();
     }
 
-   void passwordVisibilityToggleRequested() {
-       if (mPasswordToggleEnabled) {
-           // Store the current cursor position
-           final int selection = mEditText.getSelectionEnd();
+    void passwordVisibilityToggleRequested() {
+        if (mPasswordToggleEnabled) {
+            // Store the current cursor position
+            final int selection = mEditText.getSelectionEnd();
 
-           if (hasPasswordTransformation()) {
-               mEditText.setTransformationMethod(null);
-               mPasswordToggledVisible = true;
-           } else {
-               mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
-               mPasswordToggledVisible = false;
-           }
+            if (hasPasswordTransformation()) {
+                mEditText.setTransformationMethod(null);
+                mPasswordToggledVisible = true;
+            } else {
+                mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
+                mPasswordToggledVisible = false;
+            }
 
-           mPasswordToggleView.setChecked(mPasswordToggledVisible);
+            mPasswordToggleView.setChecked(mPasswordToggledVisible);
 
-           // And restore the cursor position
-           mEditText.setSelection(selection);
-       }
+            // And restore the cursor position
+            mEditText.setSelection(selection);
+        }
     }
 
     private boolean hasPasswordTransformation() {
@@ -1339,7 +1353,8 @@
         mHintExpanded = true;
     }
 
-    private void animateToExpansionFraction(final float target) {
+    @VisibleForTesting
+    void animateToExpansionFraction(final float target) {
         if (mCollapsingTextHelper.getExpansionFraction() == target) {
             return;
         }
@@ -1411,4 +1426,4 @@
         }
         return false;
     }
-}
\ No newline at end of file
+}
diff --git a/design/tests/res/values/ids.xml b/design/tests/res/values/ids.xml
index e5fcf63..52b8356 100644
--- a/design/tests/res/values/ids.xml
+++ b/design/tests/res/values/ids.xml
@@ -24,4 +24,6 @@
     <item name="page_7" type="id"/>
     <item name="page_8" type="id"/>
     <item name="page_9" type="id"/>
+    <item name="textinputlayout" type="id"/>
+    <item name="textinputedittext" type="id"/>
 </resources>
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
index 7ac3102..3923ee9 100755
--- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -21,7 +21,8 @@
 import static android.support.design.testutils.TestUtilsMatchers.withCompoundDrawable;
 import static android.support.design.testutils.TextInputLayoutActions.setError;
 import static android.support.design.testutils.TextInputLayoutActions.setErrorEnabled;
-import static android.support.design.testutils.TextInputLayoutActions.setPasswordVisibilityToggleEnabled;
+import static android.support.design.testutils.TextInputLayoutActions
+        .setPasswordVisibilityToggleEnabled;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
 import static android.support.test.espresso.action.ViewActions.typeText;
@@ -38,14 +39,18 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
+import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
 import android.support.design.test.R;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.espresso.NoMatchingViewException;
 import android.support.test.espresso.ViewAssertion;
 import android.support.test.filters.SmallTest;
+import android.util.AttributeSet;
+import android.util.SparseArray;
 import android.view.View;
 import android.widget.EditText;
 
@@ -59,6 +64,30 @@
 
     private static final String INPUT_TEXT = "Random input text";
 
+    public class TestTextInputLayout extends TextInputLayout {
+        public int animateToExpansionFractionCount = 0;
+        public float animateToExpansionFractionRecentValue = -1;
+
+        public TestTextInputLayout(Context context) {
+            super(context);
+        }
+
+        public TestTextInputLayout(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public TestTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+        }
+
+        @Override
+        protected void animateToExpansionFraction(float target) {
+            super.animateToExpansionFraction(target);
+            animateToExpansionFractionRecentValue = target;
+            animateToExpansionFractionCount++;
+        }
+    }
+
     public TextInputLayoutTest() {
         super(TextInputLayoutActivity.class);
     }
@@ -229,6 +258,35 @@
         layout.drawableStateChanged();
     }
 
+    @UiThreadTest
+    @Test
+    public void testSaveRestoreStateAnimation() {
+        final Activity activity = mActivityTestRule.getActivity();
+        final TestTextInputLayout layout = new TestTextInputLayout(activity);
+        layout.setId(R.id.textinputlayout);
+        final TextInputEditText editText = new TextInputEditText(activity);
+        editText.setText(INPUT_TEXT);
+        editText.setId(R.id.textinputedittext);
+        layout.addView(editText);
+
+        SparseArray<Parcelable> container = new SparseArray<>();
+        layout.saveHierarchyState(container);
+        layout.restoreHierarchyState(container);
+        assertEquals("Expected no animations since we simply saved/restored state",
+                0, layout.animateToExpansionFractionCount);
+
+        editText.setText("");
+        assertEquals("Expected one call to animate because we cleared text in editText",
+                1, layout.animateToExpansionFractionCount);
+        assertEquals(0f, layout.animateToExpansionFractionRecentValue, 0f);
+
+        container = new SparseArray<>();
+        layout.saveHierarchyState(container);
+        layout.restoreHierarchyState(container);
+        assertEquals("Expected no additional animations since we simply saved/restored state",
+                1, layout.animateToExpansionFractionCount);
+    }
+
     static ViewAssertion isHintExpanded(final boolean expanded) {
         return new ViewAssertion() {
             @Override
diff --git a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
index 639e906..26a8444 100644
--- a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
+++ b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
@@ -42,6 +42,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -341,7 +342,11 @@
             return;
         }
         int eventType = parser.getEventType();
-        while (eventType != XmlPullParser.END_DOCUMENT) {
+        final int innerDepth = parser.getDepth() + 1;
+
+        // Parse everything until the end of the animated-vector element.
+        while (eventType != XmlPullParser.END_DOCUMENT
+                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
             if (eventType == XmlPullParser.START_TAG) {
                 final String tagName = parser.getName();
                 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
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 f4d41b5..57c8c93 100644
--- a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -14,11 +14,7 @@
 
 package android.support.graphics.drawable;
 
-import android.support.annotation.RestrictTo;
-import android.support.v4.content.res.ResourcesCompat;
-import android.support.v4.graphics.drawable.DrawableCompat;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
 
 import android.annotation.TargetApi;
 import android.content.res.ColorStateList;
@@ -38,24 +34,27 @@
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
-import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Build;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Stack;
 
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
 /**
  * For API 24 and above, this class is delegating to the framework's {@link VectorDrawable}.
  * For older API version, this class lets you create a drawable based on an XML vector graphic.
@@ -738,7 +737,11 @@
         groupStack.push(pathRenderer.mRootGroup);
 
         int eventType = parser.getEventType();
-        while (eventType != XmlPullParser.END_DOCUMENT) {
+        final int innerDepth = parser.getDepth() + 1;
+
+        // Parse everything until the end of the vector element.
+        while (eventType != XmlPullParser.END_DOCUMENT
+                && (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
             if (eventType == XmlPullParser.START_TAG) {
                 final String tagName = parser.getName();
                 final VGroup currentGroup = groupStack.peek();
diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingStatePagerSupport.java b/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingStatePagerSupport.java
index 5863852..36a8144 100644
--- a/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingStatePagerSupport.java
+++ b/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentNestingStatePagerSupport.java
@@ -15,10 +15,6 @@
  */
 package com.example.android.supportv13.app;
 
-import java.util.ArrayList;
-
-import com.example.android.supportv13.R;
-
 import android.app.ActionBar;
 import android.app.ActionBar.Tab;
 import android.app.Activity;
@@ -29,6 +25,10 @@
 import android.support.v13.app.FragmentStatePagerAdapter;
 import android.support.v4.view.ViewPager;
 
+import com.example.android.supportv13.R;
+
+import java.util.ArrayList;
+
 //BEGIN_INCLUDE(complete)
 public class FragmentNestingStatePagerSupport extends Activity {
     ViewPager mViewPager;
@@ -101,7 +101,7 @@
             mActionBar = activity.getActionBar();
             mViewPager = pager;
             mViewPager.setAdapter(this);
-            mViewPager.setOnPageChangeListener(this);
+            mViewPager.addOnPageChangeListener(this);
         }
 
         public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentCustomAnimationSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentCustomAnimationSupport.java
index 5893521..478cb7d 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentCustomAnimationSupport.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentCustomAnimationSupport.java
@@ -16,20 +16,21 @@
 
 package com.example.android.supportv4.app;
 
-import com.example.android.supportv4.R;
-
+import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentTransaction;
-
-import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewCompat;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.TextView;
 
+import com.example.android.supportv4.R;
+
 public class FragmentCustomAnimationSupport extends FragmentActivity {
     int mStackLevel = 1;
 
@@ -39,7 +40,7 @@
         setContentView(R.layout.fragment_stack);
 
         // Watch for button clicks.
-        Button button = (Button)findViewById(R.id.new_fragment);
+        Button button = (Button) findViewById(R.id.new_fragment);
         button.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 addFragmentToStack();
@@ -119,8 +120,9 @@
                 Bundle savedInstanceState) {
             View v = inflater.inflate(R.layout.hello_world, container, false);
             View tv = v.findViewById(R.id.text);
-            ((TextView)tv).setText("Fragment #" + mNum);
-            tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
+            ((TextView) tv).setText("Fragment #" + mNum);
+            ViewCompat.setBackground(tv,
+                    ContextCompat.getDrawable(getContext(), android.R.drawable.gallery_thumb));
             return v;
         }
     }
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStackSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStackSupport.java
index 337f2c1..4115d5e 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStackSupport.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStackSupport.java
@@ -16,21 +16,22 @@
 
 package com.example.android.supportv4.app;
 
-import com.example.android.supportv4.R;
-
+import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
-
-import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewCompat;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.TextView;
 
+import com.example.android.supportv4.R;
+
 public class FragmentStackSupport extends FragmentActivity {
     int mStackLevel = 1;
 
@@ -40,13 +41,13 @@
         setContentView(R.layout.fragment_stack);
 
         // Watch for button clicks.
-        Button button = (Button)findViewById(R.id.new_fragment);
+        Button button = (Button) findViewById(R.id.new_fragment);
         button.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 addFragmentToStack();
             }
         });
-        button = (Button)findViewById(R.id.home);
+        button = (Button) findViewById(R.id.home);
         button.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 // If there is a back stack, pop it all.
@@ -128,8 +129,9 @@
                 Bundle savedInstanceState) {
             View v = inflater.inflate(R.layout.hello_world, container, false);
             View tv = v.findViewById(R.id.text);
-            ((TextView)tv).setText("Fragment #" + mNum);
-            tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
+            ((TextView) tv).setText("Fragment #" + mNum);
+            ViewCompat.setBackground(tv,
+                    ContextCompat.getDrawable(getContext(), android.R.drawable.gallery_thumb));
             return v;
         }
     }
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java
index d858fae..d9689d2 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java
@@ -32,6 +32,7 @@
 import android.support.v4.app.ListFragment;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.content.IntentCompat;
 import android.support.v4.content.Loader;
 import android.support.v4.content.pm.ActivityInfoCompat;
@@ -116,8 +117,8 @@
                 return mIcon;
             }
 
-            return mLoader.getContext().getResources().getDrawable(
-                    android.R.drawable.sym_def_app_icon);
+            return ContextCompat.getDrawable(
+                    mLoader.getContext(), android.R.drawable.sym_def_app_icon);
         }
 
         @Override public String toString() {
@@ -230,8 +231,8 @@
         @Override public List<AppEntry> loadInBackground() {
             // Retrieve all known applications.
             List<ApplicationInfo> apps = mPm.getInstalledApplications(
-                    PackageManager.GET_UNINSTALLED_PACKAGES |
-                    PackageManager.GET_DISABLED_COMPONENTS);
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS);
             if (apps == null) {
                 apps = new ArrayList<ApplicationInfo>();
             }
@@ -240,7 +241,7 @@
 
             // Create corresponding array of entries and load their labels.
             List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());
-            for (int i=0; i<apps.size(); i++) {
+            for (int i = 0; i < apps.size(); i++) {
                 AppEntry entry = new AppEntry(this, apps.get(i));
                 entry.loadLabel(context);
                 entries.add(entry);
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java b/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
index 765dc88..9fac4ab 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.session.MediaControllerCompat;
 import android.util.Log;
@@ -277,8 +278,9 @@
             holder.mTitleView.setText(item.getDescription().getTitle());
             holder.mDescriptionView.setText(item.getDescription().getDescription());
             if (item.isPlayable()) {
-                holder.mImageView.setImageDrawable(getContext().getResources()
-                        .getDrawable(R.drawable.ic_play_arrow_white_24dp));
+
+                holder.mImageView.setImageDrawable(ContextCompat.getDrawable(
+                        getContext(), R.drawable.ic_play_arrow_white_24dp));
                 holder.mImageView.setVisibility(View.VISIBLE);
             } else {
                 holder.mImageView.setVisibility(View.GONE);
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/QueueAdapter.java b/samples/Support4Demos/src/com/example/android/supportv4/media/QueueAdapter.java
index 0702e2b..3d327ff 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/QueueAdapter.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/QueueAdapter.java
@@ -16,8 +16,8 @@
 
 package com.example.android.supportv4.media;
 
-import com.example.android.supportv4.R;
 import android.app.Activity;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -26,6 +26,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.example.android.supportv4.R;
+
 import java.util.ArrayList;
 
 /**
@@ -74,10 +76,10 @@
         // If the itemId matches the active Id then use a different icon
         if (mActiveQueueItemId == item.getQueueId()) {
             holder.mImageView.setImageDrawable(
-                    getContext().getResources().getDrawable(R.drawable.ic_equalizer_white_24dp));
+                    ContextCompat.getDrawable(getContext(), R.drawable.ic_equalizer_white_24dp));
         } else {
             holder.mImageView.setImageDrawable(
-                    getContext().getResources().getDrawable(R.drawable.ic_play_arrow_white_24dp));
+                    ContextCompat.getDrawable(getContext(), R.drawable.ic_play_arrow_white_24dp));
         }
         return convertView;
     }
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/QueueFragment.java b/samples/Support4Demos/src/com/example/android/supportv4/media/QueueFragment.java
index f66447d..6ec5477 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/QueueFragment.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/QueueFragment.java
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat;
@@ -236,10 +237,10 @@
 
         if (enablePlay) {
             mPlayPause.setImageDrawable(
-                    getActivity().getResources().getDrawable(R.drawable.ic_play_arrow_white_24dp));
+                    ContextCompat.getDrawable(getActivity(), R.drawable.ic_play_arrow_white_24dp));
         } else {
             mPlayPause.setImageDrawable(
-                    getActivity().getResources().getDrawable(R.drawable.ic_pause_white_24dp));
+                    ContextCompat.getDrawable(getActivity(), R.drawable.ic_pause_white_24dp));
         }
 
         mSkipPrevious.setEnabled((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/view/CheckableFrameLayout.java b/samples/Support4Demos/src/com/example/android/supportv4/view/CheckableFrameLayout.java
index c559cf6..1ca8840 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/view/CheckableFrameLayout.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/view/CheckableFrameLayout.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.drawable.ColorDrawable;
+import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.widget.Checkable;
 import android.widget.FrameLayout;
@@ -35,7 +36,7 @@
 
     public void setChecked(boolean checked) {
         mChecked = checked;
-        setBackgroundDrawable(checked ? new ColorDrawable(0xff0000a0) : null);
+        ViewCompat.setBackground(this, checked ? new ColorDrawable(0xff0000a0) : null);
     }
 
     public boolean isChecked() {
diff --git a/v17/leanback/generatev4.py b/v17/leanback/generatev4.py
index 01c1cd9..7ba498f 100755
--- a/v17/leanback/generatev4.py
+++ b/v17/leanback/generatev4.py
@@ -19,7 +19,7 @@
 
 print "Generate v4 fragment related code for leanback"
 
-cls = ['Background', 'Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
+cls = ['Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
       'PlaybackOverlay', 'Playback', 'Rows', 'Search', 'VerticalGrid', 'Branded', 'GuidedStep', 'Onboarding']
 
 for w in cls:
diff --git a/v17/leanback/jbmr2/android/support/v17/leanback/os/TraceHelperJbmr2.java b/v17/leanback/jbmr2/android/support/v17/leanback/os/TraceHelperJbmr2.java
deleted file mode 100644
index 70b8ce9..0000000
--- a/v17/leanback/jbmr2/android/support/v17/leanback/os/TraceHelperJbmr2.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2014 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.v17.leanback.os;
-
-import android.os.Trace;
-
-class TraceHelperJbmr2 {
-
-    public static void beginSection(String section) {
-        Trace.beginSection(section);
-    }
-
-    public static void endSection() {
-        Trace.endSection();
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
index 76cfd64..f63371b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -35,7 +35,6 @@
 import android.support.annotation.ColorInt;
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.widget.BackgroundHelper;
-import android.support.v4.app.FragmentActivity;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.animation.FastOutLinearInInterpolator;
 import android.util.Log;
@@ -589,9 +588,6 @@
      * for this Activity.
      */
     public static BackgroundManager getInstance(Activity activity) {
-        if (activity instanceof FragmentActivity) {
-            return getSupportInstance((FragmentActivity) activity);
-        }
         BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
                 .findFragmentByTag(FRAGMENT_TAG);
         if (fragment != null) {
@@ -602,24 +598,10 @@
             // manager is null: this is a fragment restored by FragmentManager,
             // fall through to create a BackgroundManager attach to it.
         }
-        return new BackgroundManager(activity, false);
+        return new BackgroundManager(activity);
     }
 
-    private static BackgroundManager getSupportInstance(FragmentActivity activity) {
-        BackgroundSupportFragment fragment = (BackgroundSupportFragment) activity
-                .getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
-        if (fragment != null) {
-            BackgroundManager manager = fragment.getBackgroundManager();
-            if (manager != null) {
-                return manager;
-            }
-            // manager is null: this is a fragment restored by FragmentManager,
-            // fall through to create a BackgroundManager attach to it.
-        }
-        return new BackgroundManager(activity, true);
-    }
-
-    private BackgroundManager(Activity activity, boolean isSupportFragmentActivity) {
+    private BackgroundManager(Activity activity) {
         mContext = activity;
         mService = BackgroundContinuityService.getInstance();
         mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels;
@@ -648,11 +630,7 @@
         }
         ta.recycle();
 
-        if (isSupportFragmentActivity) {
-            createSupportFragment((FragmentActivity) activity);
-        } else {
-            createFragment(activity);
-        }
+        createFragment(activity);
     }
 
     private void createFragment(Activity activity) {
@@ -672,24 +650,6 @@
         mFragmentState = fragment;
     }
 
-    private void createSupportFragment(FragmentActivity activity) {
-        // Use a fragment to ensure the background manager gets detached properly.
-        BackgroundSupportFragment fragment = (BackgroundSupportFragment) activity
-                .getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
-        if (fragment == null) {
-            fragment = new BackgroundSupportFragment();
-            activity.getSupportFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG)
-                    .commit();
-        } else {
-            if (fragment.getBackgroundManager() != null) {
-                throw new IllegalStateException("Created duplicated BackgroundManager for same "
-                        + "activity, please use getInstance() instead");
-            }
-        }
-        fragment.setBackgroundManager(this);
-        mFragmentState = fragment;
-    }
-
     DrawableWrapper getImageInWrapper() {
         return mLayerDrawable == null
                 ? null : mLayerDrawable.findWrapperById(R.id.background_imagein);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java
deleted file mode 100644
index aef9678..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/* This file is auto-generated from BackgroundFragment.java.  DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 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.v17.leanback.app;
-
-import android.support.v4.app.Fragment;
-import android.support.annotation.RestrictTo;
-
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
-/**
- * Fragment used by the background manager.
- * @hide
- */
-@RestrictTo(GROUP_ID)
-public final class BackgroundSupportFragment extends Fragment implements
-        BackgroundManager.FragmentStateQueriable {
-    private BackgroundManager mBackgroundManager;
-
-    void setBackgroundManager(BackgroundManager backgroundManager) {
-        mBackgroundManager = backgroundManager;
-    }
-
-    BackgroundManager getBackgroundManager() {
-        return mBackgroundManager;
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        // mBackgroundManager might be null:
-        // if BackgroundSupportFragment is just restored by FragmentManager,
-        // and user does not call BackgroundManager.getInstance() yet.
-        if (mBackgroundManager != null) {
-            mBackgroundManager.onActivityStart();
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        // mBackgroundManager might be null:
-        // if BackgroundSupportFragment is just restored by FragmentManager,
-        // and user does not call BackgroundManager.getInstance() yet.
-        if (mBackgroundManager != null) {
-            mBackgroundManager.onResume();
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        // mBackgroundManager might be null:
-        // if BackgroundSupportFragment is just restored by FragmentManager,
-        // and user does not call BackgroundManager.getInstance() yet.
-        if (mBackgroundManager != null) {
-            mBackgroundManager.detach();
-        }
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/os/TraceHelper.java b/v17/leanback/src/android/support/v17/leanback/os/TraceHelper.java
deleted file mode 100644
index 4f1cd33..0000000
--- a/v17/leanback/src/android/support/v17/leanback/os/TraceHelper.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2014 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.v17.leanback.os;
-
-import android.os.Build;
-import android.support.annotation.RestrictTo;
-import android.support.v17.leanback.os.TraceHelperJbmr2;
-
-import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
-
-
-/**
- * Helper for systrace events.
- * @hide
- */
-@RestrictTo(GROUP_ID)
-public final class TraceHelper {
-
-    final static TraceHelperVersionImpl sImpl;
-
-    interface TraceHelperVersionImpl {
-        void beginSection(String section);
-        void endSection();
-    }
-
-    private static final class TraceHelperStubImpl implements TraceHelperVersionImpl {
-        TraceHelperStubImpl() {
-        }
-
-        @Override
-        public void beginSection(String section) {
-        }
-
-        @Override
-        public void endSection() {
-        }
-    }
-
-    private static final class TraceHelperJbmr2Impl implements TraceHelperVersionImpl {
-        TraceHelperJbmr2Impl() {
-        }
-
-        @Override
-        public void beginSection(String section) {
-            TraceHelperJbmr2.beginSection(section);
-        }
-
-        @Override
-        public void endSection() {
-            TraceHelperJbmr2.endSection();
-        }
-    }
-
-    private TraceHelper() {
-    }
-
-    static {
-        if (Build.VERSION.SDK_INT >= 18) {
-            sImpl = new TraceHelperJbmr2Impl();
-        } else {
-            sImpl = new TraceHelperStubImpl();
-        }
-    }
-
-    public static void beginSection(String section) {
-        sImpl.beginSection(section);
-    }
-
-    public static void endSection() {
-        sImpl.endSection();
-    }
-}
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 a52555b..1414d4a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -24,7 +24,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.support.v17.leanback.os.TraceHelper;
+import android.support.v4.os.TraceCompat;
 import android.support.v4.util.CircularIntArray;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
@@ -903,7 +903,7 @@
             return;
         }
 
-        if (TRACE) TraceHelper.beginSection("onChildSelected");
+        if (TRACE) TraceCompat.beginSection("onChildSelected");
         View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
         if (view != null) {
             RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
@@ -918,7 +918,7 @@
             }
             fireOnChildViewHolderSelected(mBaseGridView, null, NO_POSITION, 0);
         }
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
 
         // Children may request layout when a child selection event occurs (such as a change of
         // padding on the current and previously selected rows).
@@ -945,7 +945,7 @@
             return;
         }
 
-        if (TRACE) TraceHelper.beginSection("onChildSelectedAndPositioned");
+        if (TRACE) TraceCompat.beginSection("onChildSelectedAndPositioned");
         View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
         if (view != null) {
             RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
@@ -957,7 +957,7 @@
             }
             fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, null, NO_POSITION, 0);
         }
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
 
     }
 
@@ -1147,9 +1147,6 @@
             mGrid.setSpacing(mSpacingPrimary);
             detachAndScrapAttachedViews(mRecycler);
             mGrid.resetVisibleIndex();
-            if (mFocusPosition == NO_POSITION) {
-                mBaseGridView.clearFocus();
-            }
             mWindowAlignment.mainAxis().invalidateScrollMin();
             mWindowAlignment.mainAxis().invalidateScrollMax();
             if (focusViewWasInTree && firstVisibleIndex <= mFocusPosition) {
@@ -1231,7 +1228,7 @@
             return false;
         }
 
-        if (TRACE) TraceHelper.beginSection("processRowSizeSecondary");
+        if (TRACE) TraceCompat.beginSection("processRowSizeSecondary");
         CircularIntArray[] rows = mGrid == null ? null : mGrid.getItemPositionsInRows();
         boolean changed = false;
         int scrapChildWidth = -1;
@@ -1299,7 +1296,7 @@
             }
         }
 
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
         return changed;
     }
 
@@ -1441,7 +1438,7 @@
     }
 
     void measureChild(View child) {
-        if (TRACE) TraceHelper.beginSection("measureChild");
+        if (TRACE) TraceCompat.beginSection("measureChild");
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
         calculateItemDecorationsForChild(child, sTempRect);
         int widthUsed = lp.leftMargin + lp.rightMargin + sTempRect.left + sTempRect.right;
@@ -1471,7 +1468,7 @@
                     + " measuredHeight " + child.getMeasuredHeight());
         }
         if (DEBUG) Log.v(getTag(), "child lp width " + lp.width + " height " + lp.height);
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
     }
 
     /**
@@ -1500,22 +1497,22 @@
 
         @Override
         public int createItem(int index, boolean append, Object[] item) {
-            if (TRACE) TraceHelper.beginSection("createItem");
-            if (TRACE) TraceHelper.beginSection("getview");
+            if (TRACE) TraceCompat.beginSection("createItem");
+            if (TRACE) TraceCompat.beginSection("getview");
             View v = getViewForPosition(index);
-            if (TRACE) TraceHelper.endSection();
+            if (TRACE) TraceCompat.endSection();
             LayoutParams lp = (LayoutParams) v.getLayoutParams();
             RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
             lp.setItemAlignmentFacet((ItemAlignmentFacet)getFacet(vh, ItemAlignmentFacet.class));
             // See recyclerView docs:  we don't need re-add scraped view if it was removed.
             if (!lp.isItemRemoved()) {
-                if (TRACE) TraceHelper.beginSection("addView");
+                if (TRACE) TraceCompat.beginSection("addView");
                 if (append) {
                     addView(v);
                 } else {
                     addView(v, 0);
                 }
-                if (TRACE) TraceHelper.endSection();
+                if (TRACE) TraceCompat.endSection();
                 if (mChildVisibility != -1) {
                     v.setVisibility(mChildVisibility);
                 }
@@ -1582,7 +1579,7 @@
             if (DEBUG) {
                 Log.d(getTag(), "addView " + index + " " + v);
             }
-            if (TRACE) TraceHelper.endSection();
+            if (TRACE) TraceCompat.endSection();
 
             if (index == mGrid.getFirstVisibleIndex()) {
                 if (!mGrid.isReversedFlow()) {
@@ -1610,14 +1607,14 @@
 
         @Override
         public void removeItem(int index) {
-            if (TRACE) TraceHelper.beginSection("removeItem");
+            if (TRACE) TraceCompat.beginSection("removeItem");
             View v = findViewByPosition(index);
             if (mInLayout) {
                 detachAndScrapView(v, mRecycler);
             } else {
                 removeAndRecycleView(v, mRecycler);
             }
-            if (TRACE) TraceHelper.endSection();
+            if (TRACE) TraceCompat.endSection();
         }
 
         @Override
@@ -1636,7 +1633,7 @@
     };
 
     void layoutChild(int rowIndex, View v, int start, int end, int startSecondary) {
-        if (TRACE) TraceHelper.beginSection("layoutChild");
+        if (TRACE) TraceCompat.beginSection("layoutChild");
         int sizeSecondary = mOrientation == HORIZONTAL ? getDecoratedMeasuredHeightWithMargin(v)
                 : getDecoratedMeasuredWidthWithMargin(v);
         if (mFixedRowSizeSecondary > 0) {
@@ -1678,7 +1675,7 @@
         params.setOpticalInsets(left - sTempRect.left, top - sTempRect.top,
                 sTempRect.right - right, sTempRect.bottom - bottom);
         updateChildAlignments(v);
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
     }
 
     private void updateChildAlignments(View v) {
@@ -1824,12 +1821,12 @@
 
     @Override
     public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
-        if (TRACE) TraceHelper.beginSection("removeAndRecycleAllViews");
+        if (TRACE) TraceCompat.beginSection("removeAndRecycleAllViews");
         if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
         for (int i = getChildCount() - 1; i >= 0; i--) {
             removeAndRecycleViewAt(i, recycler);
         }
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
     }
 
     // called by onLayoutChildren, either focus to FocusPosition or declare focusViewAvailable
@@ -2061,7 +2058,7 @@
 
     // scroll in main direction may add/prune views
     private int scrollDirectionPrimary(int da) {
-        if (TRACE) TraceHelper.beginSection("scrollPrimary");
+        if (TRACE) TraceCompat.beginSection("scrollPrimary");
         boolean isMaxUnknown = false, isMinUnknown = false;
         int minScroll = 0, maxScroll = 0;
         if (da > 0) {
@@ -2082,13 +2079,13 @@
             }
         }
         if (da == 0) {
-            if (TRACE) TraceHelper.endSection();
+            if (TRACE) TraceCompat.endSection();
             return 0;
         }
         offsetChildrenPrimary(-da);
         mScrollOffsetPrimary += da;
         if (mInLayout) {
-            if (TRACE) TraceHelper.endSection();
+            if (TRACE) TraceCompat.endSection();
             return da;
         }
 
@@ -2103,20 +2100,20 @@
         updated = getChildCount() > childCount;
         childCount = getChildCount();
 
-        if (TRACE) TraceHelper.beginSection("remove");
+        if (TRACE) TraceCompat.beginSection("remove");
         if (mReverseFlowPrimary ? da > 0 : da < 0) {
             removeInvisibleViewsAtEnd();
         } else {
             removeInvisibleViewsAtFront();
         }
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
         updated |= getChildCount() < childCount;
         if (updated) {
             updateRowSecondarySizeRefresh();
         }
 
         mBaseGridView.invalidate();
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
         return da;
     }
 
@@ -2296,7 +2293,7 @@
 
     void scrollToSelection(int position, int subposition,
             boolean smooth, int primaryScrollExtra) {
-        if (TRACE) TraceHelper.beginSection("scrollToSelection");
+        if (TRACE) TraceCompat.beginSection("scrollToSelection");
         mPrimaryScrollExtra = primaryScrollExtra;
         View view = findViewByPosition(position);
         if (view != null) {
@@ -2322,7 +2319,7 @@
                 requestLayout();
             }
         }
-        if (TRACE) TraceHelper.endSection();
+        if (TRACE) TraceCompat.endSection();
     }
 
     void startPositionSmoothScroller(int position) {
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTestActivity.java
index 87050c1..9225ade 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTestActivity.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTestActivity.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.v17.leanback.test.R;
+import android.view.View;
 
 /**
  * Activity containing {@link DetailsFragmentTest} used for testing.
@@ -33,6 +34,8 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
+        getWindow().getDecorView().setSystemUiVisibility(uiOptions);
         setContentView(R.layout.details);
         mFragment = new DetailsTestFragment();
 
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/graphics/ChildDrawableTest.java b/v17/leanback/tests/java/android/support/v17/leanback/graphics/ChildDrawableTest.java
index aff21ff..0b0430e 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/graphics/ChildDrawableTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/graphics/ChildDrawableTest.java
@@ -21,7 +21,9 @@
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
+import android.os.Build;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -141,6 +143,7 @@
         assertEquals(expectedBounds, adjustedBounds);
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
     @Test
     public void constantState() {
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/graphics/FitWidthBitmapDrawableTest.java b/v17/leanback/tests/java/android/support/v17/leanback/graphics/FitWidthBitmapDrawableTest.java
index 0b8b5a8..a91c221 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/graphics/FitWidthBitmapDrawableTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/graphics/FitWidthBitmapDrawableTest.java
@@ -24,6 +24,8 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -64,6 +66,7 @@
         verify(canvas).drawBitmap(eq(bitmap), eq(bitmapBounds), eq(expectedDest), any(Paint.class));
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
     @Test
     public void constantState() {
         FitWidthBitmapDrawable drawable = new FitWidthBitmapDrawable();
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 8296c83..0795e16 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
@@ -27,9 +27,11 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
 import android.os.Parcelable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v17.leanback.test.R;
@@ -116,6 +118,15 @@
         Thread.sleep(500);
     }
 
+    static String dumpGridView(BaseGridView gridView) {
+        return "findFocus:" + gridView.getRootView().findFocus()
+                + " isLayoutRequested:" + gridView.isLayoutRequested()
+                + " selectedPosition:" + gridView.getSelectedPosition()
+                + " adapter.itemCount:" + gridView.getAdapter().getItemCount()
+                + " itemAnimator.isRunning:" + gridView.getItemAnimator().isRunning()
+                + " scrollState:" + gridView.getScrollState();
+    }
+
     /**
      * Change selected position.
      */
@@ -210,6 +221,17 @@
 
     protected void scrollToBegin(Runnable verify) throws Throwable {
         int key;
+        // first move to first column/row
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            key = KeyEvent.KEYCODE_DPAD_UP;
+        } else {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                key = KeyEvent.KEYCODE_DPAD_RIGHT;
+            } else {
+                key = KeyEvent.KEYCODE_DPAD_LEFT;
+            }
+        }
+        scroll(key, null);
         if (mOrientation == BaseGridView.HORIZONTAL) {
             if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
                 key = KeyEvent.KEYCODE_DPAD_RIGHT;
@@ -224,6 +246,17 @@
 
     protected void scrollToEnd(Runnable verify) throws Throwable {
         int key;
+        // first move to first column/row
+        if (mOrientation == BaseGridView.HORIZONTAL) {
+            key = KeyEvent.KEYCODE_DPAD_UP;
+        } else {
+            if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
+                key = KeyEvent.KEYCODE_DPAD_RIGHT;
+            } else {
+                key = KeyEvent.KEYCODE_DPAD_LEFT;
+            }
+        }
+        scroll(key, null);
         if (mOrientation == BaseGridView.HORIZONTAL) {
             if (mGridView.getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_RTL) {
                 key = KeyEvent.KEYCODE_DPAD_LEFT;
@@ -500,6 +533,7 @@
 
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
     @Test
     public void testItemDecorationAndMarginsAndOpticalBounds() throws Throwable {
         final int leftMargin = 3;
@@ -529,7 +563,8 @@
         final int decorationBottom = 2;
 
         final Rect opticalPaddings = new Rect();
-        mGridView.getContext().getDrawable(ninePatchDrawableResourceId).getPadding(opticalPaddings);
+        mGridView.getResources().getDrawable(ninePatchDrawableResourceId)
+                .getPadding(opticalPaddings);
         final int opticalInsetsLeft = opticalPaddings.left;
         final int opticalInsetsTop = opticalPaddings.top;
         final int opticalInsetsRight = opticalPaddings.right;
@@ -1031,7 +1066,8 @@
             }
         });
 
-        final int removeIndex = mGridView.getChildCount() - 1;
+        final int removeIndex = mGridView.getChildViewHolder(
+                mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
         mActivityTestRule.runOnUiThread(new Runnable() {
             public void run() {
                 mActivity.removeItems(removeIndex, 1);
@@ -1081,10 +1117,10 @@
         for (int i = 0; i < 20; i++) {
             sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
         }
-        Thread.sleep(100);
 
         assertTrue(mGridView.getLayoutManager().isSmoothScrolling());
-        final int removeIndex = mGridView.getChildCount() - 1;
+        final int removeIndex = mGridView.getChildViewHolder(
+                mGridView.getChildAt(mGridView.getChildCount() - 1)).getAdapterPosition();
         mActivityTestRule.runOnUiThread(new Runnable() {
             public void run() {
                 mActivity.removeItems(removeIndex, 1);
@@ -1098,7 +1134,7 @@
         waitForTransientStateGone(null);
         waitForScrollIdle();
         int focusIndex = mGridView.getSelectedPosition();
-        int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getLeft();
+        int topEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop();
 
         mActivityTestRule.runOnUiThread(new Runnable() {
             public void run() {
@@ -1107,8 +1143,8 @@
         });
         waitForTransientStateGone(null);
         waitForScrollIdle();
-        assertEquals(leftEdge,
-                mGridView.getLayoutManager().findViewByPosition(focusIndex).getLeft());
+        assertEquals(topEdge,
+                mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop());
     }
 
     @Test
@@ -1305,6 +1341,88 @@
     }
 
     @Test
+    public void removeFocusableItemAndFocusableRecyclerViewGetsFocus() throws Throwable {
+        final int numItems = 100;
+        final int numColumns = 3;
+        final int focusableIndex = 2;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = numColumns;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = false;
+        }
+        focusable[focusableIndex] = true;
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setSelectedPositionSmooth(focusableIndex);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(focusableIndex, mGridView.getSelectedPosition());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(focusableIndex, 1);
+            }
+        });
+        waitForTransientStateGone(null);
+        assertTrue(dumpGridView(mGridView), mGridView.isFocused());
+    }
+
+    @Test
+    public void removeFocusableItemAndUnFocusableRecyclerViewLosesFocus() throws Throwable {
+        final int numItems = 100;
+        final int numColumns = 3;
+        final int focusableIndex = 2;
+
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.vertical_grid);
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = numColumns;
+        boolean[] focusable = new boolean[numItems];
+        for (int i = 0; i < focusable.length; i++) {
+            focusable[i] = false;
+        }
+        focusable[focusableIndex] = true;
+        intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
+        initActivity(intent);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setFocusableInTouchMode(false);
+                mGridView.setFocusable(false);
+                mGridView.setSelectedPositionSmooth(focusableIndex);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(focusableIndex, mGridView.getSelectedPosition());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.removeItems(focusableIndex, 1);
+            }
+        });
+        waitForTransientStateGone(null);
+        assertFalse(dumpGridView(mGridView), mGridView.hasFocus());
+    }
+
+    @Test
     public void testNonFocusableVertical() throws Throwable {
         final int numItems = 200;
         final int startPos = 44;
@@ -1428,7 +1546,7 @@
     public void testTransferFocusable2() throws Throwable {
         final int numItems = 200;
         final int numColumns = 3;
-        final int startPos = 10;
+        final int startPos = 3; // make sure view at startPos is in visible area.
 
         Intent intent = new Intent();
         intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
@@ -1447,6 +1565,8 @@
         intent.putExtra(GridActivity.EXTRA_ITEMS_FOCUSABLE, focusable);
         initActivity(intent);
 
+        assertTrue(mGridView.getLayoutManager().findViewByPosition(startPos).hasFocus());
+
         changeArraySize(0);
         assertTrue(mGridView.isFocused());
 
@@ -1904,24 +2024,11 @@
         for (int i = 0; i < 20; i++) {
             sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
         }
-        Thread.sleep(100);
-        int total = 0;
         while (mGridView.getLayoutManager().isSmoothScrolling()
                 || mGridView.getScrollState() != BaseGridView.SCROLL_STATE_IDLE) {
-            if ((total += 10) >= WAIT_FOR_SCROLL_IDLE_TIMEOUT_MS) {
-                throw new RuntimeException("waitForScrollIdle Timeout");
-            }
-            try {
-                // Repeatedly pressing to make sure pending keys does not drop to zero.
-                Thread.sleep(10);
-                sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
-            } catch (InterruptedException ex) {
-                break;
-            }
+            // Repeatedly pressing to make sure pending keys does not drop to zero.
+            sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
         }
-
-        assertTrue("LinearSmoothScroller would not use many RV.smoothScrollBy() calls",
-                ((VerticalGridViewEx) mGridView).mSmoothScrollByCalled < 10);
     }
 
     @Test
diff --git a/v17/preference-leanback/res/layout/leanback_preference_widget_seekbar.xml b/v17/preference-leanback/res/layout/leanback_preference_widget_seekbar.xml
new file mode 100644
index 0000000..c121d77
--- /dev/null
+++ b/v17/preference-leanback/res/layout/leanback_preference_widget_seekbar.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- Leanback-styled Layout used for a SeekBar preference -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:minHeight="?android:attr/listPreferredItemHeight"
+              android:gravity="center_vertical"
+              android:paddingStart="@dimen/lb_preference_item_padding_start"
+              android:paddingEnd="@dimen/lb_preference_item_padding_end"
+              android:clickable="true"
+              android:focusable="true"
+              android:clipChildren="false"
+              android:clipToPadding="false">
+
+    <ImageView
+            android:id="@+android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:minWidth="@dimen/preference_icon_minWidth"/>
+
+    <RelativeLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="6dip"
+            android:layout_marginBottom="6dip"
+            android:layout_weight="1"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+
+        <TextView android:id="@+android:id/title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:layout_marginBottom="@dimen/lb_preference_item_primary_text_margin_bottom"
+                  android:fontFamily="sans-serif-condensed"
+                  android:ellipsize="marquee"
+                  android:fadingEdge="horizontal"
+                  android:textColor="@color/lb_preference_item_primary_text_color"
+                  android:textSize="@dimen/lb_preference_item_primary_text_size"/>
+
+        <TextView android:id="@+android:id/summary"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_below="@android:id/title"
+                  android:layout_alignStart="@android:id/title"
+                  android:fontFamily="sans-serif-condensed"
+                  android:textColor="@color/lb_preference_item_secondary_text_color"
+                  android:textSize="@dimen/lb_preference_item_secondary_text_size"
+                  android:maxLines="4" />
+
+        <!-- Using UnPressableLinearLayout as a workaround to disable the pressed state propagation
+        to the children of this container layout. Otherwise, the animated pressed state will also
+        play for the thumb in the AbsSeekBar in addition to the preference's ripple background.
+        The background of the SeekBar is also set to null to disable the ripple background -->
+        <android.support.v7.preference.UnPressableLinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@android:id/summary"
+                android:layout_alignStart="@android:id/title"
+                android:clipChildren="false"
+                android:clipToPadding="false">
+            <SeekBar
+                    android:id="@+id/seekbar"
+                    android:layout_width="0dp"
+                    android:layout_weight="1"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/lb_preference_seekbar_padding_start"
+                    android:paddingEnd="@dimen/lb_preference_seekbar_padding_end"
+                    android:focusable="false"
+                    android:clickable="false"
+                    android:background="@null" />
+
+            <TextView android:id="@+id/seekbar_value"
+                      android:layout_width="@dimen/lb_preference_seekbar_value_width"
+                      android:layout_height="match_parent"
+                      android:gravity="right|center_vertical"
+                      android:fontFamily="sans-serif-condensed"
+                      android:textColor="@color/lb_preference_item_primary_text_color"
+                      android:textSize="@dimen/lb_preference_item_primary_text_size"/>
+        </android.support.v7.preference.UnPressableLinearLayout>
+
+    </RelativeLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/preference-leanback/res/values/dimens.xml b/v17/preference-leanback/res/values/dimens.xml
index f3d36af..ed662a1 100644
--- a/v17/preference-leanback/res/values/dimens.xml
+++ b/v17/preference-leanback/res/values/dimens.xml
@@ -38,4 +38,7 @@
     <dimen name="lb_preference_category_height">40dp</dimen>
 
     <dimen name="lb_settings_pane_width">360dp</dimen>
+    <dimen name="lb_preference_seekbar_padding_start">0dp</dimen>
+    <dimen name="lb_preference_seekbar_padding_end">22dp</dimen>
+    <dimen name="lb_preference_seekbar_value_width">36dp</dimen>
 </resources>
diff --git a/v17/preference-leanback/res/values/styles.xml b/v17/preference-leanback/res/values/styles.xml
index fbb1ad0..cb12b3a 100644
--- a/v17/preference-leanback/res/values/styles.xml
+++ b/v17/preference-leanback/res/values/styles.xml
@@ -50,6 +50,12 @@
         <item name="android:switchTextOff">@string/v7_preference_off</item>
     </style>
 
+    <style name="LeanbackPreference.SeekBarPreference">
+        <item name="android:layout">@layout/leanback_preference_widget_seekbar</item>
+        <item name="adjustable">true</item>
+        <item name="showSeekBarValue">true</item>
+    </style>
+
     <style name="LeanbackPreference.PreferenceScreen">
     </style>
 
diff --git a/v17/preference-leanback/res/values/themes.xml b/v17/preference-leanback/res/values/themes.xml
index 591fdfa..d173b30 100644
--- a/v17/preference-leanback/res/values/themes.xml
+++ b/v17/preference-leanback/res/values/themes.xml
@@ -25,6 +25,7 @@
         <item name="preferenceInformationStyle">@style/LeanbackPreference.Information</item>
         <item name="checkBoxPreferenceStyle">@style/LeanbackPreference.CheckBoxPreference</item>
         <item name="switchPreferenceCompatStyle">@style/LeanbackPreference.SwitchPreferenceCompat</item>
+        <item name="seekBarPreferenceStyle">@style/LeanbackPreference.SeekBarPreference</item>
         <item name="switchPreferenceStyle">@style/LeanbackPreference.SwitchPreference</item>
         <item name="dialogPreferenceStyle">@style/LeanbackPreference.DialogPreference</item>
         <item name="editTextPreferenceStyle">@style/LeanbackPreference.DialogPreference.EditTextPreference</item>
diff --git a/v7/preference/res/layout/preference_widget_seekbar.xml b/v7/preference/res/layout/preference_widget_seekbar.xml
new file mode 100644
index 0000000..30bc5ff
--- /dev/null
+++ b/v7/preference/res/layout/preference_widget_seekbar.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<!-- Layout used by SeekBarPreference for the seekbar widget style. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:minHeight="?android:attr/listPreferredItemHeight"
+              android:gravity="center_vertical"
+              android:paddingEnd="?android:attr/scrollbarSize"
+              android:clipChildren="false"
+              android:clipToPadding="false">
+
+    <ImageView
+            android:id="@+android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:minWidth="@dimen/preference_icon_minWidth"/>
+
+    <RelativeLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dip"
+            android:layout_marginEnd="8dip"
+            android:layout_marginTop="6dip"
+            android:layout_marginBottom="6dip"
+            android:layout_weight="1"
+            android:clipChildren="false"
+            android:clipToPadding="false">
+
+        <TextView android:id="@+android:id/title"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:textAppearance="?android:attr/textAppearanceMedium"
+                  android:ellipsize="marquee"
+                  android:fadingEdge="horizontal"/>
+
+        <TextView android:id="@+android:id/summary"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:layout_below="@android:id/title"
+                  android:layout_alignStart="@android:id/title"
+                  android:textAppearance="?android:attr/textAppearanceSmall"
+                  android:textColor="?android:attr/textColorSecondary"
+                  android:maxLines="4"/>
+
+        <!-- Using UnPressableLinearLayout as a workaround to disable the pressed state propagation
+        to the children of this container layout. Otherwise, the animated pressed state will also
+        play for the thumb in the AbsSeekBar in addition to the preference's ripple background.
+        The background of the SeekBar is also set to null to disable the ripple background -->
+        <android.support.v7.preference.UnPressableLinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@android:id/summary"
+                android:layout_alignStart="@android:id/title"
+                android:clipChildren="false"
+                android:clipToPadding="false">
+            <SeekBar
+                    android:id="@+id/seekbar"
+                    android:layout_width="0dp"
+                    android:layout_weight="1"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/preference_seekbar_padding_start"
+                    android:paddingEnd="@dimen/preference_seekbar_padding_end"
+                    android:focusable="false"
+                    android:clickable="false"
+                    android:background="@null" />
+
+            <TextView android:id="@+id/seekbar_value"
+                      android:layout_width="@dimen/preference_seekbar_value_width"
+                      android:layout_height="match_parent"
+                      android:gravity="right|center_vertical"
+                      android:fontFamily="sans-serif-condensed"
+                      android:singleLine="true"
+                      android:textAppearance="?android:attr/textAppearanceMedium"
+                      android:ellipsize="marquee"
+                      android:fadingEdge="horizontal"/>
+        </android.support.v7.preference.UnPressableLinearLayout>
+
+    </RelativeLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/v7/preference/res/values/attrs.xml b/v7/preference/res/values/attrs.xml
index bd64109..baf2a03 100644
--- a/v7/preference/res/values/attrs.xml
+++ b/v7/preference/res/values/attrs.xml
@@ -254,4 +254,20 @@
         <attr name="selectableItemBackground" />
     </declare-styleable>
 
+    <declare-styleable name="SeekBarPreference">
+        <attr name="min" format="integer"/>
+        <attr name="android:max"/>
+        <attr name="android:layout" />
+        <!-- Attribute indicating whether the slider within this preference can be adjusted, that is
+        pressing left/right keys when this preference is focused will move the slider accordingly
+        (e.g. inline adjustable preferences). False, if the slider within the preference is
+        read-only and cannot be adjusted. By default, the seekbar is adjustable. -->
+        <attr name="adjustable" format="boolean" />
+        <!-- Flag indicating whether the TextView next to the seekbar that shows the current seekbar
+        value will be displayed. If true, the view is VISIBLE; if false, the view will be GONE.
+        By default, this view is VISIBLE. -->
+        <attr name="showSeekBarValue" format="boolean" />
+    </declare-styleable>
+
+
 </resources>
diff --git a/v7/preference/res/values/dimens.xml b/v7/preference/res/values/dimens.xml
new file mode 100644
index 0000000..4816e36
--- /dev/null
+++ b/v7/preference/res/values/dimens.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Minimum space to allocate to the left of a preference item for an icon.
+    This helps in aligning titles when some items have icons and some don't. When space is
+    at a premium, we don't pre-allocate any space. -->
+    <dimen name="preference_icon_minWidth">0dp</dimen>
+    <!-- The padding to the left of the seekbar view within a SeekBarPreference -->
+    <dimen name="preference_seekbar_padding_start">0dp</dimen>
+    <!-- The padding to the right of the seekbar view within a SeekBarPreference -->
+    <dimen name="preference_seekbar_padding_end">22dp</dimen>
+    <!-- The width of the TextView indicating the current value of the SeekBarPreference -->
+    <dimen name="preference_seekbar_value_width">36dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/v7/preference/res/values/styles.xml b/v7/preference/res/values/styles.xml
index 06b24fa..e61a361 100644
--- a/v7/preference/res/values/styles.xml
+++ b/v7/preference/res/values/styles.xml
@@ -49,6 +49,12 @@
         <item name="android:switchTextOff">@string/v7_preference_off</item>
     </style>
 
+    <style name="Preference.SeekBarPreference">
+        <item name="android:layout">@layout/preference_widget_seekbar</item>
+        <item name="adjustable">true</item>
+        <item name="showSeekBarValue">true</item>
+    </style>
+
     <style name="Preference.PreferenceScreen">
     </style>
 
diff --git a/v7/preference/res/values/themes.xml b/v7/preference/res/values/themes.xml
index bb7f496..af5f469 100644
--- a/v7/preference/res/values/themes.xml
+++ b/v7/preference/res/values/themes.xml
@@ -24,6 +24,7 @@
         <item name="preferenceInformationStyle">@style/Preference.Information</item>
         <item name="checkBoxPreferenceStyle">@style/Preference.CheckBoxPreference</item>
         <item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat</item>
+        <item name="seekBarPreferenceStyle">@style/Preference.SeekBarPreference</item>
         <item name="dialogPreferenceStyle">@style/Preference.DialogPreference</item>
         <item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference</item>
         <item name="preferenceFragmentListStyle">@style/PreferenceFragmentList</item>
diff --git a/v7/preference/src/android/support/v7/preference/SeekBarPreference.java b/v7/preference/src/android/support/v7/preference/SeekBarPreference.java
new file mode 100644
index 0000000..6bfb523
--- /dev/null
+++ b/v7/preference/src/android/support/v7/preference/SeekBarPreference.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2016 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.preference;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+/**
+ * Preference based on android.preference.SeekBarPreference but uses support v7 preference as base.
+ * It contains a title and a seekbar and an optional seekbar value TextView. The actual preference
+ * layout is customizable by setting {@code android:layout} on the preference widget layout or
+ * {@code seekBarPreferenceStyle} attribute.
+ * The seekbar within the preference can be defined adjustable or not by setting {@code
+ * adjustable} attribute. If adjustable, the preference will be responsive to DPAD left/right keys.
+ * Otherwise, it skips those keys.
+ * The seekbar value view can be shown or disabled by setting {@code showSeekBarValue} attribute
+ * to true or false, respectively.
+ * Other SeekBar specific attributes (e.g. {@code title, summary, defaultValue, min, max}) can be
+ * set directly on the preference widget layout.
+ */
+public class SeekBarPreference extends Preference {
+
+    private int mSeekBarValue;
+    private int mMin;
+    private int mMax;
+    private boolean mTrackingTouch;
+    private SeekBar mSeekBar;
+    private TextView mSeekBarValueTextView;
+    private boolean mAdjustable; // whether the seekbar should respond to the left/right keys
+    private boolean mShowSeekBarValue; // whether to show the seekbar value TextView next to the bar
+
+    private static final String TAG = "SeekBarPreference";
+
+    /**
+     * Listener reacting to the SeekBar changing value by the user
+     */
+    private OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            if (fromUser && !mTrackingTouch) {
+                syncValueInternal(seekBar);
+            }
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            mTrackingTouch = true;
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            mTrackingTouch = false;
+            if (seekBar.getProgress() + mMin != mSeekBarValue) {
+                syncValueInternal(seekBar);
+            }
+        }
+    };
+
+    /**
+     * Listener reacting to the user pressing DPAD left/right keys if {@code
+     * adjustable} attribute is set to true; it transfers the key presses to the SeekBar
+     * to be handled accordingly.
+     */
+    private View.OnKeyListener mSeekBarKeyListener = new View.OnKeyListener() {
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (event.getAction() != KeyEvent.ACTION_DOWN) {
+                return false;
+            }
+            if (!mAdjustable && (keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
+                    keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) {
+                // Right or left keys are pressed when in non-adjustable mode; Skip the keys.
+                return false;
+            }
+
+            // We don't want to propagate the click keys down to the seekbar view since it will create
+            // the ripple effect for the thumb.
+            if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
+                return false;
+            }
+
+            if (mSeekBar == null) {
+                Log.e(TAG, "SeekBar view is null and hence cannot be adjusted.");
+                return false;
+            }
+            return mSeekBar.onKeyDown(keyCode, event);
+        }
+    };
+
+    public SeekBarPreference(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes);
+
+        /**
+         * The ordering of these two statements are important. If we want to set max first, we need
+         * to perform the same steps by changing min/max to max/min as following:
+         * mMax = a.getInt(...) and setMin(...).
+         */
+        mMin = a.getInt(R.styleable.SeekBarPreference_min, 0);
+        setMax(a.getInt(R.styleable.SeekBarPreference_android_max, 100));
+        mAdjustable = a.getBoolean(R.styleable.SeekBarPreference_adjustable, true);
+        mShowSeekBarValue = a.getBoolean(R.styleable.SeekBarPreference_showSeekBarValue, true);
+        a.recycle();
+    }
+
+    public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SeekBarPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.seekBarPreferenceStyle);
+    }
+
+    public SeekBarPreference(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+        view.itemView.setOnKeyListener(mSeekBarKeyListener);
+        mSeekBar = (SeekBar) view.findViewById(R.id.seekbar);
+        mSeekBarValueTextView = (TextView) view.findViewById(R.id.seekbar_value);
+        if (mShowSeekBarValue) {
+            mSeekBarValueTextView.setVisibility(View.VISIBLE);
+        } else {
+            mSeekBarValueTextView.setVisibility(View.GONE);
+            mSeekBarValueTextView = null;
+        }
+
+        if (mSeekBar == null) {
+            Log.e(TAG, "SeekBar view is null in onBindViewHolder.");
+            return;
+        }
+        mSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
+        mSeekBar.setMax(mMax - mMin);
+        mSeekBar.setProgress(mSeekBarValue - mMin);
+        if (mSeekBarValueTextView != null) {
+            mSeekBarValueTextView.setText(String.valueOf(mSeekBarValue));
+        }
+        mSeekBar.setEnabled(isEnabled());
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        setValue(restoreValue ? getPersistedInt(mSeekBarValue)
+                : (Integer) defaultValue);
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray a, int index) {
+        return a.getInt(index, 0);
+    }
+
+    public void setMin(int min) {
+        if (min > mMax) {
+            min = mMax;
+        }
+        if (min != mMin) {
+            mMin = min;
+            notifyChanged();
+        }
+    }
+
+    public int getMin() {
+        return mMin;
+    }
+
+    public void setMax(int max) {
+        if (max < mMin) {
+            max = mMin;
+        }
+        if (max != mMax) {
+            mMax = max;
+            notifyChanged();
+        }
+    }
+
+    public int getMax() {
+        return mMax;
+    }
+
+    public void setAdjustable(boolean adjustable) {
+        mAdjustable = adjustable;
+    }
+
+    public boolean isAdjustable() {
+        return mAdjustable;
+    }
+
+    public void setValue(int seekBarValue) {
+        setValueInternal(seekBarValue, true);
+    }
+
+    private void setValueInternal(int seekBarValue, boolean notifyChanged) {
+        if (seekBarValue < mMin) {
+            seekBarValue = mMin;
+        }
+        if (seekBarValue > mMax) {
+            seekBarValue = mMax;
+        }
+
+        if (seekBarValue != mSeekBarValue) {
+            mSeekBarValue = seekBarValue;
+            if (mSeekBarValueTextView != null) {
+                mSeekBarValueTextView.setText(String.valueOf(mSeekBarValue));
+            }
+            persistInt(seekBarValue);
+            if (notifyChanged) {
+                notifyChanged();
+            }
+        }
+    }
+
+    public int getValue() {
+        return mSeekBarValue;
+    }
+
+    /**
+     * Persist the seekBar's seekbar value if callChangeListener
+     * returns true, otherwise set the seekBar's value to the stored value
+     */
+    private void syncValueInternal(SeekBar seekBar) {
+        int seekBarValue = mMin + seekBar.getProgress();
+        if (seekBarValue != mSeekBarValue) {
+            if (callChangeListener(seekBarValue)) {
+                setValueInternal(seekBarValue, false);
+            } else {
+                seekBar.setProgress(mSeekBarValue - mMin);
+            }
+        }
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        if (isPersistent()) {
+            // No need to save instance state since it's persistent
+            return superState;
+        }
+
+        // Save the instance state
+        final SavedState myState = new SavedState(superState);
+        myState.seekBarValue = mSeekBarValue;
+        myState.min = mMin;
+        myState.max = mMax;
+        return myState;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        if (!state.getClass().equals(SavedState.class)) {
+            // Didn't save state for us in onSaveInstanceState
+            super.onRestoreInstanceState(state);
+            return;
+        }
+
+        // Restore the instance state
+        SavedState myState = (SavedState) state;
+        super.onRestoreInstanceState(myState.getSuperState());
+        mSeekBarValue = myState.seekBarValue;
+        mMin = myState.min;
+        mMax = myState.max;
+        notifyChanged();
+    }
+
+    /**
+     * SavedState, a subclass of {@link BaseSavedState}, will store the state
+     * of MyPreference, a subclass of Preference.
+     * <p>
+     * It is important to always call through to super methods.
+     */
+    private static class SavedState extends BaseSavedState {
+        int seekBarValue;
+        int min;
+        int max;
+
+        public SavedState(Parcel source) {
+            super(source);
+
+            // Restore the click counter
+            seekBarValue = source.readInt();
+            min = source.readInt();
+            max = source.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+
+            // Save the click counter
+            dest.writeInt(seekBarValue);
+            dest.writeInt(min);
+            dest.writeInt(max);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @SuppressWarnings("unused")
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+}
diff --git a/v7/preference/src/android/support/v7/preference/UnPressableLinearLayout.java b/v7/preference/src/android/support/v7/preference/UnPressableLinearLayout.java
new file mode 100644
index 0000000..7129cd7
--- /dev/null
+++ b/v7/preference/src/android/support/v7/preference/UnPressableLinearLayout.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 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.preference;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.support.annotation.RestrictTo;
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
+/**
+ * Custom LinearLayout that does not propagate the pressed state down to its children.
+ * By default, the pressed state is propagated to all the children that are not clickable
+ * or long-clickable.
+ * @hide
+ */
+@RestrictTo(GROUP_ID)
+public class UnPressableLinearLayout extends LinearLayout {
+    public UnPressableLinearLayout(Context context) {
+        this(context, null);
+    }
+
+    public UnPressableLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void dispatchSetPressed(boolean pressed) {
+        // Skip dispatching the pressed key state to the children so that they don't trigger any
+        // pressed state animation on their stateful drawables.
+    }
+}
diff --git a/v7/preference/tests/NO_DOCS b/v7/preference/tests/NO_DOCS
new file mode 100644
index 0000000..db956bc
--- /dev/null
+++ b/v7/preference/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
\ No newline at end of file
diff --git a/v7/recyclerview/src/android/support/v7/widget/GapWorker.java b/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
index cf43129..eee1995 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
@@ -24,10 +24,88 @@
 class GapWorker implements Runnable {
     static final ThreadLocal<GapWorker> sGapWorker = new ThreadLocal<>();
 
-    ArrayList<RecyclerView> mRecyclerViews = new ArrayList<>();
+    private ArrayList<RecyclerView> mRecyclerViews = new ArrayList<>();
     long mPostTimeNs;
     long mFrameIntervalNs;
 
+    /**
+     * Prefetch information associated with a specfic RecyclerView.
+     */
+    static class PrefetchRegistryImpl implements RecyclerView.PrefetchRegistry {
+        private int mPrefetchDx;
+        private int mPrefetchDy;
+        int[] mPrefetchArray;
+
+        int mCount;
+
+        void setPrefetchVector(int dx, int dy) {
+            mPrefetchDx = dx;
+            mPrefetchDy = dy;
+        }
+
+        void collectPrefetchPositionsFromView(RecyclerView view) {
+            mCount = 0;
+            if (mPrefetchArray != null) {
+                Arrays.fill(mPrefetchArray, -1);
+            }
+
+            final RecyclerView.LayoutManager layout = view.mLayout;
+            if (view.mAdapter != null
+                    && layout != null
+                    && layout.isItemPrefetchEnabled()
+                    && !view.hasPendingAdapterUpdates()) {
+                layout.collectPrefetchPositions(mPrefetchDx, mPrefetchDy, view.mState, this);
+                if (mCount > layout.mPrefetchMaxCountObserved) {
+                    layout.mPrefetchMaxCountObserved = mCount;
+                    view.mRecycler.updateViewCacheSize();
+                }
+            }
+        }
+
+        @Override
+        public void addPosition(int layoutPosition, int pixelDistance) {
+            if (pixelDistance < 0) {
+                throw new IllegalArgumentException("Pixel distance must be non-negative");
+            }
+
+            // allocate or expand array as needed, doubling when needed
+            final int storagePosition = mCount * 2;
+            if (mPrefetchArray == null) {
+                mPrefetchArray = new int[4];
+                Arrays.fill(mPrefetchArray, -1);
+            } else if (storagePosition >= mPrefetchArray.length) {
+                final int[] oldArray = mPrefetchArray;
+                mPrefetchArray = new int[storagePosition * 2];
+                System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length);
+            }
+
+            // add position
+            mPrefetchArray[storagePosition] = layoutPosition;
+            mPrefetchArray[storagePosition + 1] = pixelDistance;
+
+            mCount++;
+        }
+
+        boolean lastPrefetchIncludedPosition(int position) {
+            if (mPrefetchArray != null) {
+                final int count = mCount * 2;
+                for (int i = 0; i < count; i += 2) {
+                    if (mPrefetchArray[i] == position) return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Called when prefetch indices are no longer valid for cache prioritization.
+         */
+        void clearPrefetchPositions() {
+            if (mPrefetchArray != null) {
+                Arrays.fill(mPrefetchArray, -1);
+            }
+        }
+    }
+
     public void add(RecyclerView recyclerView) {
         if (RecyclerView.DEBUG && mRecyclerViews.contains(recyclerView)) {
             throw new IllegalStateException("RecyclerView already present in worker list!");
@@ -56,74 +134,35 @@
             }
         }
 
-        recyclerView.mPrefetchDx = prefetchDx;
-        recyclerView.mPrefetchDy = prefetchDy;
-    }
-
-    static boolean lastPrefetchIncludedPosition(RecyclerView recyclerView, int position) {
-        if (recyclerView.mPrefetchArray != null) {
-            for (int i = 0; i < recyclerView.mPrefetchArray.length; i++) {
-                if (recyclerView.mPrefetchArray[i] == position) return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Called when prefetch indices are no longer valid for cache prioritization.
-     */
-    static void clearPrefetchPositions(RecyclerView recyclerView) {
-        if (recyclerView.mPrefetchArray != null) {
-            Arrays.fill(recyclerView.mPrefetchArray, -1);
-        }
+        recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
     }
 
     static void layoutPrefetch(long deadlineNs, RecyclerView view) {
-        final int prefetchCount = view.mLayout.getItemPrefetchCount();
-        if (view.mAdapter == null
-                || view.mLayout == null
-                || !view.mLayout.isItemPrefetchEnabled()
-                || prefetchCount < 1
-                || view.hasPendingAdapterUpdates()) {
-            // abort - no work
-            return;
-        }
+        final RecyclerView.Recycler recycler = view.mRecycler;
+        final PrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry;
 
-        if (view.mPrefetchArray == null
-                || view.mPrefetchArray.length < prefetchCount) {
-            view.mPrefetchArray = new int[prefetchCount];
-        }
-        Arrays.fill(view.mPrefetchArray, -1);
-        int viewCount = view.mLayout.gatherPrefetchIndices(
-                view.mPrefetchDx, view.mPrefetchDy,
-                view.mState, view.mPrefetchArray);
-        layoutPrefetchImpl(deadlineNs, view.mRecycler, view.mPrefetchArray, viewCount);
-    }
+        prefetchRegistry.collectPrefetchPositionsFromView(view);
 
-    static void layoutPrefetchImpl(long deadlineNs, RecyclerView.Recycler recycler,
-            int[] prefetchArray, int viewCount) {
-        if (viewCount == 0) return;
+        final int count = prefetchRegistry.mCount;
+        for (int i = 0; i < count; i++) {
+            int pos = prefetchRegistry.mPrefetchArray[i * 2];
+            if (pos < 0) {
+                throw new IllegalArgumentException("Invalid prefetch position requested: " + pos);
+            }
 
-        int childPosition = prefetchArray[viewCount - 1];
-        if (childPosition < 0) {
-            throw new IllegalArgumentException("Invalid prefetch position requested: "
-                    + childPosition);
-        }
-        RecyclerView.ViewHolder holder = recycler.tryGetViewHolderForPositionByDeadline(
-                childPosition, false, deadlineNs);
-        if (viewCount > 1) {
-            layoutPrefetchImpl(deadlineNs, recycler, prefetchArray, viewCount - 1);
-        }
-        if (holder != null) {
-            if (holder.isBound()) {
-                // Only give the view a chance to go into the cache if binding succeeded
-                recycler.recycleViewHolderInternal(holder);
-            } else {
-                // Didn't bind, so we can't cache the view, but it will stay in the pool until next
-                // prefetch/traversal. If a View fails to bind, it means we didn't have enough time
-                // prior to the deadline (and won't for other instances of this type, during this
-                // GapWorker pass).
-                recycler.addViewHolderToRecycledViewPool(holder);
+            RecyclerView.ViewHolder holder = recycler.tryGetViewHolderForPositionByDeadline(
+                    pos, false, deadlineNs);
+            if (holder != null) {
+                if (holder.isBound()) {
+                    // Only give the view a chance to go into the cache if binding succeeded
+                    recycler.recycleViewHolderInternal(holder);
+                } else {
+                    // Didn't bind, so we can't cache the view, but it will stay in the pool until
+                    // next prefetch/traversal. If a View fails to bind, it means we didn't have
+                    // enough time prior to the deadline (and won't for other instances of this
+                    // type, during this GapWorker prefetch pass).
+                    recycler.addViewHolderToRecycledViewPool(holder);
+                }
             }
         }
     }
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index 6365017..c4a72d3 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -505,24 +505,18 @@
     }
 
     @Override
-    int getItemPrefetchCount() {
-        return mSpanCount;
-    }
-
-    @Override
-    int gatherPrefetchIndicesForLayoutState(RecyclerView.State state, LayoutState layoutState,
-                int[] outIndices) {
+    void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
+            RecyclerView.PrefetchRegistry prefetchRegistry) {
         int remainingSpan = mSpanCount;
         int count = 0;
         while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
             final int pos = layoutState.mCurrentPosition;
-            outIndices[count] = pos;
+            prefetchRegistry.addPosition(pos, layoutState.mScrollingOffset);
             final int spanSize = mSpanSizeLookup.getSpanSize(pos);
             remainingSpan -= spanSize;
             layoutState.mCurrentPosition += layoutState.mItemDirection;
             count++;
         }
-        return count;
     }
 
     @Override
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index d21406c..1f4682f 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -1182,34 +1182,28 @@
                 && mOrientationHelper.getEnd() == 0;
     }
 
-    @Override
-    int getItemPrefetchCount() {
-        return 1;
-    }
-
-    int gatherPrefetchIndicesForLayoutState(RecyclerView.State state, LayoutState layoutState,
-            int[] outIndices) {
+    void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
+            RecyclerView.PrefetchRegistry prefetchRegistry) {
         final int pos = layoutState.mCurrentPosition;
         if (pos >= 0 && pos < state.getItemCount()) {
-            outIndices[0] = pos;
-            return 1;
+            prefetchRegistry.addPosition(pos, layoutState.mScrollingOffset);
         }
-        return 0;
     }
 
+    /** @hide */
     @Override
-    int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
+    public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+            RecyclerView.PrefetchRegistry prefetchRegistry) {
         int delta = (mOrientation == HORIZONTAL) ? dx : dy;
         if (getChildCount() == 0 || delta == 0) {
             // can't support this scroll, so don't bother prefetching
-            return 0;
+            return;
         }
 
-
         final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
         final int absDy = Math.abs(delta);
         updateLayoutState(layoutDirection, absDy, true, state);
-        return gatherPrefetchIndicesForLayoutState(state, mLayoutState, outIndices);
+        collectPrefetchPositionsForLayoutState(state, mLayoutState, prefetchRegistry);
     }
 
     int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index c72feec..d9a3046 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -419,11 +419,8 @@
     final ViewFlinger mViewFlinger = new ViewFlinger();
 
     GapWorker mGapWorker;
-
-    // Following mPrefetchXXX fields are all owned by mGapWorker
-    int mPrefetchDx;
-    int mPrefetchDy;
-    int[] mPrefetchArray;
+    GapWorker.PrefetchRegistryImpl mPrefetchRegistry =
+            ALLOW_THREAD_GAP_WORK ? new GapWorker.PrefetchRegistryImpl() : null;
 
     final State mState = new State();
 
@@ -2791,7 +2788,7 @@
                             vtev)) {
                         getParent().requestDisallowInterceptTouchEvent(true);
                     }
-                    if (ALLOW_THREAD_GAP_WORK) {
+                    if (mGapWorker != null) {
                         mGapWorker.postFromTraversal(this, dx, dy);
                     }
                 }
@@ -4587,11 +4584,11 @@
                 if (scroller.isFinished() || !fullyConsumedAny) {
                     setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
                     if (ALLOW_THREAD_GAP_WORK) {
-                        GapWorker.clearPrefetchPositions(RecyclerView.this);
+                        mPrefetchRegistry.clearPrefetchPositions();
                     }
                 } else {
                     postOnAnimation();
-                    if (ALLOW_THREAD_GAP_WORK) {
+                    if (mGapWorker != null) {
                         mGapWorker.postFromTraversal(RecyclerView.this, dx, dy);
                     }
                 }
@@ -5009,11 +5006,9 @@
         }
 
         void updateViewCacheSize() {
-            int extraCache = 0;
-            if (mLayout != null && ALLOW_THREAD_GAP_WORK) {
-                extraCache = mLayout.isItemPrefetchEnabled() ? mLayout.getItemPrefetchCount() : 0;
-            }
+            int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
             mViewCacheMax = mRequestedCacheMax + extraCache;
+
             // first, try the views that can be recycled
             for (int i = mCachedViews.size() - 1;
                     i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
@@ -5444,7 +5439,7 @@
             }
             mCachedViews.clear();
             if (ALLOW_THREAD_GAP_WORK) {
-                GapWorker.clearPrefetchPositions(RecyclerView.this);
+                mPrefetchRegistry.clearPrefetchPositions();
             }
         }
 
@@ -5519,14 +5514,12 @@
                     int targetCacheIndex = cachedViewSize;
                     if (ALLOW_THREAD_GAP_WORK
                             && cachedViewSize > 0
-                            && !GapWorker.lastPrefetchIncludedPosition(
-                                    RecyclerView.this, holder.mPosition)) {
+                            && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                         // when adding the view, skip past most recently prefetched views
                         int cacheIndex = cachedViewSize - 1;
                         while (cacheIndex >= 0) {
                             int cachedPos = mCachedViews.get(cacheIndex).mPosition;
-                            if (!GapWorker.lastPrefetchIncludedPosition(
-                                    RecyclerView.this, cachedPos)) {
+                            if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                                 break;
                             }
                             cacheIndex--;
@@ -6617,6 +6610,12 @@
         private boolean mItemPrefetchEnabled = true;
 
         /**
+         * Written by {@link GapWorker} when prefetches occur to track largest number of view ever
+         * requested by a {@link #collectPrefetchPositions(int, int, State, PrefetchRegistry)} call.
+         */
+        int mPrefetchMaxCountObserved;
+
+        /**
          * These measure specs might be the measure specs that were passed into RecyclerView's
          * onMeasure method OR fake measure specs created by the RecyclerView.
          * For example, when a layout is run, RecyclerView always sets these specs to be
@@ -6918,6 +6917,7 @@
         public final void setItemPrefetchEnabled(boolean enabled) {
             if (enabled != mItemPrefetchEnabled) {
                 mItemPrefetchEnabled = enabled;
+                mPrefetchMaxCountObserved = 0;
                 if (mRecyclerView != null) {
                     mRecyclerView.mRecycler.updateViewCacheSize();
                 }
@@ -6936,11 +6936,28 @@
             return mItemPrefetchEnabled;
         }
 
-        int getItemPrefetchCount() { return 0; }
-
-        int gatherPrefetchIndices(int dx, int dy, State state, int[] outIndices) {
-            return 0;
-        }
+        /**
+         * Gather all positions from the LayoutManager to be prefetched.
+         *
+         * <p>If item prefetch is enabled, this method is called in between traversals to gather
+         * which positions the LayoutManager will soon need, given upcoming movement in subsequent
+         * traversals.</p>
+         *
+         * <p>The LayoutManager should call {@link PrefetchRegistry#addPosition(int, int)} for each
+         * item to be prepared, and these positions will have their ViewHolders created and bound
+         * in advance of being needed by a scroll or layout.</p>
+         *
+         * @param dx X movement component.
+         * @param dy Y movement component.
+         * @param state State of RecyclerView
+         * @param prefetchRegistry PrefetchRegistry to add prefetch entries into.
+         *
+         * @see #isItemPrefetchEnabled()
+         *
+         * @hide
+         */
+        public void collectPrefetchPositions(int dx, int dy, State state,
+                PrefetchRegistry prefetchRegistry) {}
 
         void dispatchAttachedToWindow(RecyclerView view) {
             mIsAttachedToWindow = true;
@@ -9367,6 +9384,24 @@
     }
 
     /**
+     * Interface for LayoutManagers to request items to be prefetched, based on position, with
+     * specified distance from viewport, which indicates priority.
+     *
+     * @hide
+     */
+    public interface PrefetchRegistry {
+        /**
+         * Requests an an item to be prefetched, based on position, with a specified distance,
+         * indicating priority.
+         *
+         * @param layoutPosition Position of the item to prefetch.
+         * @param pixelDistance Distance from the current viewport to the bounds of the item,
+         *                      must be non-negative.
+         */
+        void addPosition(int layoutPosition, int pixelDistance);
+    }
+
+    /**
      * An ItemDecoration allows the application to add a special drawing and layout offset
      * to specific item views from the adapter's data set. This can be useful for drawing dividers
      * between items, highlights, visual grouping boundaries and more.
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index eba10ca..7040bcc 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -212,6 +212,12 @@
      */
     private boolean mSmoothScrollbarEnabled = true;
 
+    /**
+     * Temporary array used (solely in {@link #collectPrefetchPositions}) for stashing and sorting
+     * distances to views being prefetched.
+     */
+    private int[] mPrefetchDistances;
+
     private final Runnable mCheckForGapsRunnable = new Runnable() {
         @Override
         public void run() {
@@ -2065,29 +2071,44 @@
         requestLayout();
     }
 
+    /** @hide */
     @Override
-    int getItemPrefetchCount() {
-        return mSpanCount;
-    }
-
-    @Override
-    int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
+    public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+            RecyclerView.PrefetchRegistry prefetchRegistry) {
+        /* This method uses the simplifying assumption that the next N items (where N = span count)
+         * will be assigned, one-to-one, to spans, where ordering is based on which span  extends
+         * least beyond the viewport.
+         *
+         * While this simplified model will be incorrect in some cases, it's difficult to know
+         * item heights, or whether individual items will be full span prior to construction.
+         *
+         * While this greedy estimation approach may underestimate the distance to prefetch items,
+         * it's very unlikely to overestimate them, so distances can be conservatively used to know
+         * the soonest (in terms of scroll distance) a prefetched view may come on screen.
+         */
         int delta = (mOrientation == HORIZONTAL) ? dx : dy;
         if (getChildCount() == 0 || delta == 0) {
             // can't support this scroll, so don't bother prefetching
-            return 0;
+            return;
         }
         prepareLayoutStateForDelta(delta, state);
-        int remainingSpan = mSpanCount;
-        int count = 0;
-        while (count < mSpanCount && mLayoutState.hasMore(state) && remainingSpan > 0) {
-            final int pos = mLayoutState.mCurrentPosition;
-            outIndices[count] = pos;
-            remainingSpan--;
-            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
-            count++;
+
+        // build sorted list of distances to end of each span (though we don't care which is which)
+        if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) {
+            mPrefetchDistances = new int[mSpanCount];
         }
-        return count;
+        for (int i = 0; i < mSpanCount; i++) {
+            mPrefetchDistances[i] = mLayoutState.mItemDirection == LAYOUT_START
+                    ? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine)
+                    : mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine;
+        }
+        Arrays.sort(mPrefetchDistances, 0, mSpanCount);
+
+        // then assign them in order to the next N views (where N = span count)
+        for (int i = 0; i < mSpanCount && mLayoutState.hasMore(state); i++) {
+            prefetchRegistry.addPosition(mLayoutState.mCurrentPosition, mPrefetchDistances[i]);
+            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+        }
     }
 
     void prepareLayoutStateForDelta(int delta, RecyclerView.State state) {
@@ -2104,8 +2125,7 @@
         updateLayoutState(referenceChildPosition, state);
         setLayoutStateDirection(layoutDir);
         mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
-        final int absDt = Math.abs(delta);
-        mLayoutState.mAvailable = absDt;
+        mLayoutState.mAvailable = Math.abs(delta);
     }
 
     int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
@@ -2136,12 +2156,12 @@
         return totalScroll;
     }
 
-    private int getLastChildPosition() {
+    int getLastChildPosition() {
         final int childCount = getChildCount();
         return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
     }
 
-    private int getFirstChildPosition() {
+    int getFirstChildPosition() {
         final int childCount = getChildCount();
         return childCount == 0 ? 0 : getPosition(getChildAt(0));
     }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
index 1d05852..8d93553 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -318,9 +318,10 @@
         }
 
         @Override
-        int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
+        public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+                RecyclerView.PrefetchRegistry prefetchRegistry) {
             if (prefetchLatch != null) prefetchLatch.countDown();
-            return super.gatherPrefetchIndices(dx, dy, state, outIndices);
+            super.collectPrefetchPositions(dx, dy, state, prefetchRegistry);
         }
     }
 
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
index c9c2e7e..9bea02c 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
@@ -635,9 +635,10 @@
         }
 
         @Override
-        int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
+        public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+                RecyclerView.PrefetchRegistry prefetchRegistry) {
             if (prefetchLatch != null) prefetchLatch.countDown();
-            return super.gatherPrefetchIndices(dx, dy, state, outIndices);
+            super.collectPrefetchPositions(dx, dy, state, prefetchRegistry);
         }
     }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
index 9915981..ea88391 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
@@ -805,9 +805,10 @@
         }
 
         @Override
-        int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
+        public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+                RecyclerView.PrefetchRegistry prefetchRegistry) {
             if (prefetchLatch != null) prefetchLatch.countDown();
-            return super.gatherPrefetchIndices(dx, dy, state, outIndices);
+            super.collectPrefetchPositions(dx, dy, state, prefetchRegistry);
         }
     }
 
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java
index ec6ba23..71f93e6 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java
@@ -52,7 +52,9 @@
     @Test
     public void gridCachedBorderstTest() throws Throwable {
         RecyclerView recyclerView = setupBasic(mConfig);
-        waitForFirstLayout(recyclerView);
+        mGlm.expectLayout(1);
+        setRecyclerView(recyclerView);
+        mGlm.waitForLayout(10);
         final boolean vertical = mConfig.mOrientation == GridLayoutManager.VERTICAL;
         final int expectedSizeSum = vertical ? recyclerView.getWidth() : recyclerView.getHeight();
         final int lastVisible = mGlm.findLastVisibleItemPosition();
@@ -69,7 +71,7 @@
     }
 
     private static List<Config> cachedBordersTestConfigs() {
-        ArrayList<Config> configs = new ArrayList<Config>();
+        ArrayList<Config> configs = new ArrayList<>();
         final int[] spanCounts = new int[]{88, 279, 741};
         final int[] spanPerItem = new int[]{11, 9, 13};
         for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
index 2c5244b..73e1753 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -30,7 +30,6 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.AttributeSet;
@@ -363,19 +362,42 @@
         assertSame(RecyclerView.sQuinticInterpolator, mRecyclerView.mViewFlinger.mInterpolator);
     }
 
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
     @Test
     public void prefetchChangesCacheSize() {
+        mRecyclerView.setAdapter(new MockAdapter(20));
         MockLayoutManager mlm = new MockLayoutManager() {
             @Override
-            int getItemPrefetchCount() {
-                return 3;
+            public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+                    RecyclerView.PrefetchRegistry prefetchManager) {
+                prefetchManager.addPosition(0, 0);
+                prefetchManager.addPosition(1, 0);
+                prefetchManager.addPosition(2, 0);
             }
         };
+
         RecyclerView.Recycler recycler = mRecyclerView.mRecycler;
         assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE, recycler.mViewCacheMax);
         mRecyclerView.setLayoutManager(mlm);
-        assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE + 3, recycler.mViewCacheMax);
+        assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE, recycler.mViewCacheMax);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            // layout, so prefetches can occur
+            mRecyclerView.measure(View.MeasureSpec.EXACTLY | 100, View.MeasureSpec.EXACTLY | 100);
+            mRecyclerView.layout(0, 0, 100, 100);
+
+            // prefetch gets 3 items, so expands cache by 3
+            mRecyclerView.mPrefetchRegistry.collectPrefetchPositionsFromView(mRecyclerView);
+            assertEquals(3, mRecyclerView.mPrefetchRegistry.mCount);
+            assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE + 3, recycler.mViewCacheMax);
+
+            // Reset to default by removing layout
+            mRecyclerView.setLayoutManager(null);
+            assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE, recycler.mViewCacheMax);
+
+            // And restore by restoring layout
+            mRecyclerView.setLayoutManager(mlm);
+            assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE + 3, recycler.mViewCacheMax);
+        }
     }
 
     static class MockLayoutManager extends RecyclerView.LayoutManager {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
index 45bcf13..8712e22 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.widget;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
@@ -88,16 +89,11 @@
             }
 
             @Override
-            int getItemPrefetchCount() {
-                return 3;
-            }
-
-            @Override
-            int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
-                outIndices[0] = 0;
-                outIndices[1] = 1;
-                outIndices[2] = 2;
-                return 3;
+            public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+                    RecyclerView.PrefetchRegistry prefetchManager) {
+                prefetchManager.addPosition(0, 0);
+                prefetchManager.addPosition(1, 0);
+                prefetchManager.addPosition(2, 0);
             }
 
             @Override
@@ -155,7 +151,15 @@
 
     @Test
     public void prefetchItemsNotEvictedWithInserts() {
-        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3));
+        mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), 3) {
+            @Override
+            public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+                    RecyclerView.PrefetchRegistry prefetchRegistry) {
+                prefetchRegistry.addPosition(0, 0);
+                prefetchRegistry.addPosition(1, 0);
+                prefetchRegistry.addPosition(2, 0);
+            }
+        });
 
         RecyclerView.Adapter mockAdapter = mock(RecyclerView.Adapter.class);
         when(mockAdapter.onCreateViewHolder(any(ViewGroup.class), anyInt()))
@@ -173,8 +177,10 @@
         mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 320);
         mRecyclerView.layout(0, 0, 320, 320);
 
-        mRecyclerView.mPrefetchArray = new int[] { 0, 1, 2 };
-        GapWorker.layoutPrefetchImpl(Long.MAX_VALUE, mRecycler, mRecyclerView.mPrefetchArray, 3);
+        assertEquals(2, mRecyclerView.mRecycler.mViewCacheMax);
+        GapWorker.layoutPrefetch(RecyclerView.FOREVER_NS, mRecyclerView);
+        assertEquals(5, mRecyclerView.mRecycler.mViewCacheMax);
+
         verifyCacheContainsPositions(0, 1, 2);
 
         // further views recycled, as though from scrolling, shouldn't evict prefetched views:
@@ -231,8 +237,7 @@
         assertTrue(mRecycler.mCachedViews.isEmpty());
 
         // rows 0, 1, and 2 are all attached and visible. Prefetch row 3:
-        mRecyclerView.mPrefetchDx = 0;
-        mRecyclerView.mPrefetchDy = 1;
+        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
         GapWorker.layoutPrefetch(RecyclerView.FOREVER_NS, mRecyclerView);
 
         // row 3 is cached:
@@ -259,8 +264,7 @@
                 View view = new View(getContext());
                 view.setMinimumWidth(100);
                 view.setMinimumHeight(100);
-                RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(view) {};
-                return holder;
+                return new RecyclerView.ViewHolder(view) {};
             }
 
             @Override
@@ -285,14 +289,13 @@
         final long deadlineNs = mRecyclerView.getNanoTime() + TimeUnit.MILLISECONDS.toNanos(19);
 
         // Timed prefetch
-        mRecyclerView.mPrefetchDx = 0;
-        mRecyclerView.mPrefetchDy = 1;
+        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
         GapWorker.layoutPrefetch(deadlineNs, mRecyclerView);
 
         // will have enough time to inflate/bind one view, and inflate another
         assertTrue(mRecycler.mCachedViews.size() == 1);
         assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 1);
-        verifyCacheContainsPositions(11); // Note: order/view here is an implementation detail
+        verifyCacheContainsPositions(9); // Note: order/view here is an implementation detail
 
 
         // Unbounded prefetch this time
@@ -303,4 +306,92 @@
         assertTrue(mRecyclerView.getRecycledViewPool().getRecycledViewCount(0) == 0);
         verifyCacheContainsPositions(9, 10, 11);
     }
-}
\ No newline at end of file
+
+    private void verifyPositionsPrefetched(RecyclerView.LayoutManager lm, int dx, int dy,
+            Integer[] ... positionData) {
+        RecyclerView.PrefetchRegistry prefetchRegistry = mock(RecyclerView.PrefetchRegistry.class);
+        lm.collectPrefetchPositions(dx, dy, mRecyclerView.mState, prefetchRegistry);
+
+        verify(prefetchRegistry, times(positionData.length)).addPosition(anyInt(), anyInt());
+        for (Integer[] aPositionData : positionData) {
+            verify(prefetchRegistry).addPosition(aPositionData[0], aPositionData[1]);
+        }
+    }
+
+    @Test
+    public void prefetchStaggeredItemsPriority() {
+        StaggeredGridLayoutManager sglm =
+                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
+        mRecyclerView.setLayoutManager(sglm);
+
+        // first view 50x100 pixels, rest are 100x100 so second column is offset
+        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
+            @Override
+            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                return new RecyclerView.ViewHolder(new View(getContext())) {};
+            }
+
+            @Override
+            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+                holder.itemView.setMinimumWidth(100);
+                holder.itemView.setMinimumHeight(position == 0 ? 50 : 100);
+            }
+
+            @Override
+            public int getItemCount() {
+                return 100;
+            }
+        });
+
+        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 200, View.MeasureSpec.AT_MOST | 200);
+        mRecyclerView.layout(0, 0, 200, 200);
+
+        /* Each row is 50 pixels:
+         * ------------- *
+         *   0   |   1   *
+         *   2   |   1   *
+         *   2   |   3   *
+         *___4___|___3___*
+         *   4   |   5   *
+         *   6   |   5   *
+         *      ...      *
+         */
+        assertEquals(5, mRecyclerView.getChildCount());
+        assertEquals(0, sglm.getFirstChildPosition());
+        assertEquals(4, sglm.getLastChildPosition());
+
+        // prefetching down shows 5 at 0 pixels away, 6 at 50 pixels away
+        verifyPositionsPrefetched(sglm, 0, 10,
+                new Integer[] {5, 0}, new Integer[] {6, 50});
+
+        // Prefetch upward shows nothing
+        verifyPositionsPrefetched(sglm, 0, -10);
+
+        mRecyclerView.scrollBy(0, 100);
+
+        /* Each row is 50 pixels:
+         * ------------- *
+         *   0   |   1   *
+         *___2___|___1___*
+         *   2   |   3   *
+         *   4   |   3   *
+         *   4   |   5   *
+         *___6___|___5___*
+         *   6   |   7   *
+         *   8   |   7   *
+         *      ...      *
+         */
+
+        assertEquals(5, mRecyclerView.getChildCount());
+        assertEquals(2, sglm.getFirstChildPosition());
+        assertEquals(6, sglm.getLastChildPosition());
+
+        // prefetching down shows 7 at 0 pixels away, 8 at 50 pixels away
+        verifyPositionsPrefetched(sglm, 0, 10,
+                new Integer[] {7, 0}, new Integer[] {8, 50});
+
+        // prefetching up shows 1 is 0 pixels away, 0 at 50 pixels away
+        verifyPositionsPrefetched(sglm, 0, -10,
+                new Integer[] {1, 0}, new Integer[] {0, 50});
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewPrefetchTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewPrefetchTest.java
index 820b205..b3f44a8 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewPrefetchTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewPrefetchTest.java
@@ -57,15 +57,10 @@
         }
 
         @Override
-        int getItemPrefetchCount() {
-            return 1;
-        }
-
-        @Override
-        int gatherPrefetchIndices(int dx, int dy, RecyclerView.State state, int[] outIndices) {
+        public void collectPrefetchPositions(int dx, int dy, RecyclerView.State state,
+                RecyclerView.PrefetchRegistry prefetchRegistry) {
             prefetchLatch.countDown();
-            outIndices[0] = 6;
-            return 1;
+            prefetchRegistry.addPosition(6, 0);
         }
 
         void waitForPrefetch(int time) throws InterruptedException {