Merge "Add ability to jump-scroll to a particular preference" into nyc-dev
diff --git a/Android.mk b/Android.mk
index 0d8e2f9..2ef2319 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,11 +21,57 @@
 api_check_current_msg_file := $(LOCAL_PATH)/apicheck_msg_current.txt
 api_check_last_msg_file := $(LOCAL_PATH)/apicheck_msg_last.txt
 
+###########################################################
+# Find all of the files in the given subdirs that match the
+# specified pattern but do not match another pattern. This
+# function uses $(1) instead of LOCAL_PATH as the base.
+# $(1): the base dir, relative to the root of the source tree.
+# $(2): the file name pattern to match.
+# $(3): the file name pattern to exclude.
+# $(4): a list of subdirs of the base dir.
+# Returns: a list of paths relative to the base dir.
+###########################################################
+
+define find-files-in-subdirs-exclude
+$(sort $(patsubst ./%,%, \
+  $(shell cd $(1) ; \
+          find -L $(4) -name $(2) -and -not -name $(3) -and -not -name ".*") \
+ ))
+endef
+
+###########################################################
+## Find all of the files under the named directories where
+## the file name matches the specified pattern but does not
+## match another pattern. Meant to be used like:
+##    SRC_FILES := $(call all-named-files-under,.*\.h,src tests)
+###########################################################
+
+define all-named-files-under-exclude
+$(call find-files-in-subdirs-exclude,$(LOCAL_PATH),"$(1)","$(2)",$(3))
+endef
+
+###########################################################
+## Find all of the files under the current directory where
+## the file name matches the specified pattern but does not
+## match another pattern.
+###########################################################
+
+define all-subdir-named-files-exclude
+$(call all-named-files-under-exclude,$(1),$(2),.)
+endef
+
+
+# Pre-process support library AIDLs
+aidl_files := $(addprefix $(LOCAL_PATH)/, $(call all-subdir-named-files-exclude,*.aidl,I*.aidl))
+support-aidl := $(TARGET_OUT_COMMON_INTERMEDIATES)/support.aidl
+$(support-aidl): $(aidl_files) | $(AIDL)
+	$(AIDL) --preprocess $@ $(aidl_files)
+
 .PHONY: update-support-api
 .PHONY: check-support-api
 
 # Run the check-support-api task on a SDK build
-sdk: check-support-api
+sdk: check-support-api $(support-aidl)
 
 # Build all support libraries
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 317bb03..7b2174e 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -45,11 +45,7 @@
 #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
 
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/support.aidl)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/annotations/Android.mk b/annotations/Android.mk
index df16cdb..78a29df 100644
--- a/annotations/Android.mk
+++ b/annotations/Android.mk
@@ -15,8 +15,11 @@
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-annotations
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/annotations/AndroidManifest.xml b/annotations/AndroidManifest.xml
new file mode 100644
index 0000000..6f24ecb
--- /dev/null
+++ b/annotations/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="android.support.annotations" />
diff --git a/build.gradle b/build.gradle
index 9aa0557..8bfa9c4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@
         maven { url "../../prebuilts/maven_repo/android" }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:1.5.0'
+        classpath 'com.android.tools.build:gradle:2.1.0-alpha4'
     }
 }
 
@@ -114,6 +114,7 @@
 Archive.Arch=ANY\n\
 Extra.NameDisplay=Android Support Repository\n\
 Archive.Os=ANY\n\
+Pkg.Desc=Local Maven repository for Support Libraries\n\
 Pkg.Revision=${project.ext.extraVersion}.0.0\n\
 Extra.VendorId=android"
 
diff --git a/design/Android.mk b/design/Android.mk
index c4004f5..c9ba6ec 100644
--- a/design/Android.mk
+++ b/design/Android.mk
@@ -14,22 +14,23 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# Android libraries referenced by this module's resources.
+resource_libs := \
+    android-support-v7-appcompat \
+    android-support-v7-recyclerview
+
 # Build the resources using the latest applicable SDK version.
 # We do this here because the final static library must be compiled with an older
 # SDK version than the resources.  The resources library and the R class that it
 # contains will not be linked into the final static library.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-design-res
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
-    frameworks/support/v7/appcompat/res \
-    frameworks/support/v7/recyclerview/res
-LOCAL_AAPT_FLAGS := \
-    --auto-add-overlay \
-    --extra-packages android.support.v7.appcompat \
-    --extra-packages android.support.v7.recyclerview \
-    --no-version-vectors
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := $(resource_libs)
+LOCAL_AAPT_FLAGS := --no-version-vectors
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -42,7 +43,8 @@
 LOCAL_MODULE := android-support-design-base
 LOCAL_SDK_VERSION := 7
 LOCAL_SRC_FILES := $(call all-java-files-under, base)
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+    android-support-design-res \
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
@@ -57,7 +59,8 @@
 LOCAL_SDK_VERSION := 7
 LOCAL_SRC_FILES := $(call all-java-files-under, eclair-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-base
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+    android-support-design-res \
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
@@ -72,7 +75,8 @@
 LOCAL_SDK_VERSION := 11
 LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-eclair-mr1
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+    android-support-design-res \
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
@@ -87,7 +91,8 @@
 LOCAL_SDK_VERSION := 12
 LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb-mr1)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+    android-support-design-res \
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
@@ -102,7 +107,8 @@
 LOCAL_SDK_VERSION := 14
 LOCAL_SRC_FILES := $(call all-java-files-under, ics)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb-mr1
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+    android-support-design-res \
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
@@ -117,7 +123,8 @@
 LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, lollipop)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-ics
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+    android-support-design-res \
     android-support-v4 \
     android-support-v7-appcompat \
     android-support-v7-recyclerview
@@ -127,19 +134,26 @@
 support_module_src_files += $(LOCAL_SRC_FILES)
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-design \
+#       android-support-v7-appcompat \
+#       android-support-v7-recyclerview \
+#       android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-design
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-lollipop
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
-    android-support-v4 \
-    android-support-v7-appcompat \
-    android-support-v7-recyclerview
+LOCAL_STATIC_ANDROID_LIBRARIES := android-support-design-res
+LOCAL_SHARED_ANDROID_LIBRARIES := $(resource_libs) android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/design/api/current.txt b/design/api/current.txt
index b68507a..2fc889b 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -4,18 +4,19 @@
     ctor public AppBarLayout(android.content.Context);
     ctor public AppBarLayout(android.content.Context, android.util.AttributeSet);
     method public void addOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
-    method public float getTargetElevation();
+    method public deprecated float getTargetElevation();
     method public final int getTotalScrollRange();
     method public void removeOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
     method public void setExpanded(boolean);
     method public void setExpanded(boolean, boolean);
-    method public void setTargetElevation(float);
+    method public deprecated void setTargetElevation(float);
   }
 
   public static class AppBarLayout.Behavior extends android.support.design.widget.HeaderBehavior {
     ctor public AppBarLayout.Behavior();
     ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int);
+    method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int, int, int, int);
     method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, float, float, boolean);
     method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int[]);
     method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int, int);
@@ -261,6 +262,7 @@
     ctor public FloatingActionButton.Behavior();
     method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
     method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
+    method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, int);
   }
 
diff --git a/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java b/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java
index f080592..cb9fc88 100644
--- a/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java
+++ b/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java
@@ -16,13 +16,65 @@
 
 package android.support.design.widget;
 
+import android.animation.AnimatorInflater;
+import android.animation.ObjectAnimator;
+import android.animation.StateListAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.design.R;
+import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
 class ViewUtilsLollipop {
 
+    private static final int[] STATE_LIST_ANIM_ATTRS = new int[] {android.R.attr.stateListAnimator};
+
     static void setBoundsViewOutlineProvider(View view) {
         view.setOutlineProvider(ViewOutlineProvider.BOUNDS);
     }
 
+    static void setStateListAnimatorFromAttrs(View view, AttributeSet attrs,
+           int defStyleAttr,  int defStyleRes) {
+        final Context context = view.getContext();
+        final TypedArray a = context.obtainStyledAttributes(attrs, STATE_LIST_ANIM_ATTRS,
+                defStyleAttr, defStyleRes);
+        try {
+            if (a.hasValue(0)) {
+                StateListAnimator sla = AnimatorInflater.loadStateListAnimator(context,
+                        a.getResourceId(0, 0));
+                view.setStateListAnimator(sla);
+            }
+        } finally {
+            a.recycle();
+        }
+    }
+
+    /**
+     * Creates and sets a {@link StateListAnimator} with a custom elevation value
+     */
+    static void setDefaultAppBarLayoutStateListAnimator(final View view,
+            final float targetElevation) {
+        final StateListAnimator sla = new StateListAnimator();
+
+        // Enabled, collapsible and collapsed == elevated
+        sla.addState(new int[]{android.R.attr.enabled, R.attr.state_collapsible,
+                        R.attr.state_collapsed},
+                ObjectAnimator.ofFloat(view, "elevation", targetElevation));
+
+        // Enabled and collapsible, but not collapsed != elevated
+        sla.addState(new int[]{android.R.attr.enabled, R.attr.state_collapsible,
+                        -R.attr.state_collapsed},
+                ObjectAnimator.ofFloat(view, "elevation", 0f));
+
+        // Enabled but not collapsible == elevated
+        sla.addState(new int[]{android.R.attr.enabled, -R.attr.state_collapsible},
+                ObjectAnimator.ofFloat(view, "elevation", targetElevation));
+
+        // Default, none elevated state
+        sla.addState(new int[0], ObjectAnimator.ofFloat(view, "elevation", 0));
+
+        view.setStateListAnimator(sla);
+    }
+
 }
diff --git a/design/res/anim-v21/design_appbar_state_list_animator.xml b/design/res/anim-v21/design_appbar_state_list_animator.xml
new file mode 100644
index 0000000..c7bba14
--- /dev/null
+++ b/design/res/anim-v21/design_appbar_state_list_animator.xml
@@ -0,0 +1,43 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item android:state_enabled="true" app:state_collapsible="true" app:state_collapsed="true">
+        <objectAnimator android:propertyName="elevation"
+                        android:valueTo="@dimen/design_appbar_elevation"
+                        android:valueType="floatType"/>
+    </item>
+
+    <item android:state_enabled="true" app:state_collapsible="true" app:state_collapsed="false">
+        <objectAnimator android:propertyName="elevation"
+                        android:valueTo="0dp"
+                        android:valueType="floatType"/>
+    </item>
+
+    <item android:state_enabled="true" app:state_collapsible="false">
+        <objectAnimator android:propertyName="elevation"
+                        android:valueTo="@dimen/design_appbar_elevation"
+                        android:valueType="floatType"/>
+    </item>
+
+    <item>
+        <objectAnimator android:propertyName="elevation"
+                        android:valueTo="0"
+                        android:valueType="floatType"/>
+    </item>
+
+</selector>
\ No newline at end of file
diff --git a/design/res/values-v21/styles.xml b/design/res/values-v21/styles.xml
new file mode 100644
index 0000000..76bde78
--- /dev/null
+++ b/design/res/values-v21/styles.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<resources>
+
+    <style name="Widget.Design.AppBarLayout" parent="Base.Widget.Design.AppBarLayout">
+        <item name="android:stateListAnimator">@anim/design_appbar_state_list_animator</item>
+    </style>
+
+</resources>
+
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
index f3abec1..4e54db4 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -210,6 +210,7 @@
     </declare-styleable>
 
     <declare-styleable name="AppBarLayout">
+        <!-- Deprecated. Elevation is now controlled via a state list animator. -->
         <attr name="elevation" />
         <attr name="android:background" />
         <!-- The initial expanded state for the AppBarLayout. This only takes effect when this
@@ -217,6 +218,15 @@
         <attr name="expanded" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="AppBarLayoutStates">
+        <!-- State value for {@link android.support.design.widget.AppBarLayout} set when the view
+             has been collapsed. -->
+        <attr name="state_collapsed" format="boolean" />
+        <!-- State value for {@link android.support.design.widget.AppBarLayout} set when the view
+             has children which are capable of being collapsed. -->
+        <attr name="state_collapsible" format="boolean" />
+    </declare-styleable>
+
     <declare-styleable name="AppBarLayout_LayoutParams">
         <attr name="layout_scrollFlags">
             <!-- The view will be scroll in direct relation to scroll events. This flag needs to be
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index 18fdf7f..d49764f 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -101,11 +101,13 @@
         <item name="statusBarScrim">?attr/colorPrimaryDark</item>
     </style>
 
-    <style name="Widget.Design.AppBarLayout" parent="android:Widget">
-        <item name="elevation">@dimen/design_appbar_elevation</item>
+    <style name="Base.Widget.Design.AppBarLayout" parent="android:Widget">
         <item name="android:background">?attr/colorPrimary</item>
     </style>
 
+    <style name="Widget.Design.AppBarLayout" parent="Base.Widget.Design.AppBarLayout">
+    </style>
+
     <style name="Widget.Design.CoordinatorLayout" parent="android:Widget">
         <item name="statusBarBackground">?attr/colorPrimaryDark</item>
     </style>
diff --git a/design/src/android/support/design/internal/NavigationMenuItemView.java b/design/src/android/support/design/internal/NavigationMenuItemView.java
index 0423345..1e2c928 100644
--- a/design/src/android/support/design/internal/NavigationMenuItemView.java
+++ b/design/src/android/support/design/internal/NavigationMenuItemView.java
@@ -108,7 +108,8 @@
 
     private StateListDrawable createDefaultBackground() {
         TypedValue value = new TypedValue();
-        if (getContext().getTheme().resolveAttribute(R.attr.colorControlHighlight, value, true)) {
+        if (getContext().getTheme().resolveAttribute(
+                android.support.v7.appcompat.R.attr.colorControlHighlight, value, true)) {
             StateListDrawable drawable = new StateListDrawable();
             drawable.addState(CHECKED_STATE_SET, new ColorDrawable(value.data));
             drawable.addState(EMPTY_STATE_SET, new ColorDrawable(Color.TRANSPARENT));
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 2419de9..03a125b 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.annotation.IntDef;
@@ -124,15 +125,18 @@
     private int mDownPreScrollRange = INVALID_SCROLL_RANGE;
     private int mDownScrollRange = INVALID_SCROLL_RANGE;
 
-    boolean mHaveChildWithInterpolator;
-
-    private float mTargetElevation;
+    private boolean mHaveChildWithInterpolator;
 
     private int mPendingAction = PENDING_ACTION_NONE;
 
     private WindowInsetsCompat mLastInsets;
 
-    private final List<OnOffsetChangedListener> mListeners;
+    private List<OnOffsetChangedListener> mListeners;
+
+    private boolean mCollapsible;
+    private boolean mCollapsed;
+
+    private final int[] mTmpStatesArray = new int[2];
 
     public AppBarLayout(Context context) {
         this(context, null);
@@ -144,22 +148,29 @@
 
         ThemeUtils.checkAppCompatTheme(context);
 
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout,
+        if (Build.VERSION.SDK_INT >= 21) {
+            // Use the bounds view outline provider so that we cast a shadow, even without a
+            // background
+            ViewUtilsLollipop.setBoundsViewOutlineProvider(this);
+
+            // If we're running on API 21+, we should reset any state list animator from our
+            // default style
+            ViewUtilsLollipop.setStateListAnimatorFromAttrs(this, attrs, 0,
+                    R.style.Widget_Design_AppBarLayout);
+        }
+
+        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout,
                 0, R.style.Widget_Design_AppBarLayout);
-        mTargetElevation = a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0);
         setBackgroundDrawable(a.getDrawable(R.styleable.AppBarLayout_android_background));
         if (a.hasValue(R.styleable.AppBarLayout_expanded)) {
             setExpanded(a.getBoolean(R.styleable.AppBarLayout_expanded, false));
         }
+        if (Build.VERSION.SDK_INT >= 21 && a.hasValue(R.styleable.AppBarLayout_elevation)) {
+            ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(
+                    this, a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0));
+        }
         a.recycle();
 
-        // Use the bounds view outline provider so that we cast a shadow, even without a background
-        ViewUtils.setBoundsViewOutlineProvider(this);
-
-        mListeners = new ArrayList<>();
-
-        ViewCompat.setElevation(this, mTargetElevation);
-
         ViewCompat.setOnApplyWindowInsetsListener(this,
                 new android.support.v4.view.OnApplyWindowInsetsListener() {
                     @Override
@@ -178,6 +189,9 @@
      * @see #removeOnOffsetChangedListener(OnOffsetChangedListener)
      */
     public void addOnOffsetChangedListener(OnOffsetChangedListener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<>();
+        }
         if (listener != null && !mListeners.contains(listener)) {
             mListeners.add(listener);
         }
@@ -189,7 +203,7 @@
      * @param listener the listener to remove.
      */
     public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
-        if (listener != null) {
+        if (mListeners != null && listener != null) {
             mListeners.remove(listener);
         }
     }
@@ -216,6 +230,19 @@
                 break;
             }
         }
+
+        updateCollapsible();
+    }
+
+    private void updateCollapsible() {
+        boolean haveCollapsibleChild = false;
+        for (int i = 0, z = getChildCount(); i < z; i++) {
+            if (((LayoutParams) getChildAt(i).getLayoutParams()).isCollapsible()) {
+                haveCollapsibleChild = true;
+                break;
+            }
+        }
+        setCollapsible(haveCollapsibleChild);
     }
 
     private void invalidateScrollRanges() {
@@ -422,6 +449,19 @@
         return mDownScrollRange = Math.max(0, range);
     }
 
+    private void dispatchOffsetUpdates(int offset) {
+        // Iterate backwards through the list so that most recently added listeners
+        // get the first chance to decide
+        if (mListeners != null) {
+            for (int i = 0, z = mListeners.size(); i < z; i++) {
+                final OnOffsetChangedListener listener = mListeners.get(i);
+                if (listener != null) {
+                    listener.onOffsetChanged(this, offset);
+                }
+            }
+        }
+    }
+
     final int getMinimumHeightForVisibleOverlappingContent() {
         final int topInset = getTopInset();
         final int minHeight = ViewCompat.getMinimumHeight(this);
@@ -432,33 +472,66 @@
 
         // Otherwise, we'll use twice the min height of our last child
         final int childCount = getChildCount();
-        return childCount >= 1
-                ? (ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) * 2) + topInset
-                : 0;
+        final int lastChildMinHeight = childCount >= 1
+                ? ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) : 0;
+        if (lastChildMinHeight != 0) {
+            return (lastChildMinHeight * 2) + topInset;
+        }
+
+        // If we reach here then we don't have a min height explicitly set. Instead we'll take a
+        // guess at 1/3 of our height being visible
+        return getHeight() / 3;
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] extraStates = mTmpStatesArray;
+        final int[] states = super.onCreateDrawableState(extraSpace + extraStates.length);
+
+        extraStates[0] = mCollapsible ? R.attr.state_collapsible : -R.attr.state_collapsible;
+        extraStates[1] = mCollapsible && mCollapsed
+                ? R.attr.state_collapsed : -R.attr.state_collapsed;
+
+        return mergeDrawableStates(states, extraStates);
+    }
+
+    private void setCollapsible(boolean collapsible) {
+        if (mCollapsible != collapsible) {
+            mCollapsible = collapsible;
+            refreshDrawableState();
+        }
+    }
+
+    private void setCollapsed(boolean collapsed) {
+        if (mCollapsed != collapsed) {
+            mCollapsed = collapsed;
+            refreshDrawableState();
+        }
     }
 
     /**
-     * Set the elevation value to use when this {@link AppBarLayout} should be elevated
-     * above content.
-     * <p>
-     * This method does not do anything itself. A typical use for this method is called from within
-     * an {@link OnOffsetChangedListener} when the offset has changed in such a way to require an
-     * elevation change.
+     * @deprecated target elevation is now deprecated. AppBarLayout's elevation is now
+     * controlled via a {@link android.animation.StateListAnimator}. If a target
+     * elevation is set, either by this method or the {@code app:elevation} attibute,
+     * a new state list animator is created which uses the given {@code elevation} value.
      *
-     * @param elevation the elevation value to use.
-     *
-     * @see ViewCompat#setElevation(View, float)
+     * @attr ref android.support.design.R.styleable#AppBarLayout_elevation
      */
+    @Deprecated
     public void setTargetElevation(float elevation) {
-        mTargetElevation = elevation;
+        if (Build.VERSION.SDK_INT >= 21) {
+            ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(this, elevation);
+        }
     }
 
     /**
-     * Returns the elevation value to use when this {@link AppBarLayout} should be elevated
-     * above content.
+     * @deprecated target elevation is now deprecated. AppBarLayout's elevation is now
+     * controlled via a {@link android.animation.StateListAnimator}. This method now
+     * always returns 0.
      */
+    @Deprecated
     public float getTargetElevation() {
-        return mTargetElevation;
+        return 0;
     }
 
     private int getPendingAction() {
@@ -550,6 +623,8 @@
          */
         static final int FLAG_QUICK_RETURN = SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS;
         static final int FLAG_SNAP = SCROLL_FLAG_SCROLL | SCROLL_FLAG_SNAP;
+        static final int COLLAPSIBLE_FLAGS = SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
+                | SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED;
 
         int mScrollFlags = SCROLL_FLAG_SCROLL;
         Interpolator mScrollInterpolator;
@@ -643,6 +718,14 @@
         public Interpolator getScrollInterpolator() {
             return mScrollInterpolator;
         }
+
+        /**
+         * Returns true if the scroll flags are compatible for 'collapsing'
+         */
+        private boolean isCollapsible() {
+            return (mScrollFlags & SCROLL_FLAG_SCROLL) == SCROLL_FLAG_SCROLL
+                    && (mScrollFlags & COLLAPSIBLE_FLAGS) != 0;
+        }
     }
 
     /**
@@ -876,6 +959,27 @@
         }
 
         @Override
+        public boolean onMeasureChild(CoordinatorLayout parent, AppBarLayout child,
+                int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
+                int heightUsed) {
+            final CoordinatorLayout.LayoutParams lp =
+                    (CoordinatorLayout.LayoutParams) child.getLayoutParams();
+            if (lp.height == CoordinatorLayout.LayoutParams.WRAP_CONTENT) {
+                // If the view is set to wrap on it's height, CoordinatorLayout by default will
+                // cap the view at the CoL's height. Since the AppBarLayout can scroll, this isn't
+                // what we actually want, so we measure it ourselves with an unspecified spec to
+                // allow the child to be larger than it's parent
+                parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed,
+                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed);
+                return true;
+            }
+
+            // Let the parent handle it as normal
+            return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed,
+                    parentHeightMeasureSpec, heightUsed);
+        }
+
+        @Override
         public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl,
                 int layoutDirection) {
             boolean handled = super.onLayoutChild(parent, abl, layoutDirection);
@@ -917,8 +1021,8 @@
             setTopAndBottomOffset(
                     MathUtils.constrain(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0));
 
-            // Make sure we update the elevation
-            dispatchOffsetUpdates(abl);
+            // Make sure we dispatch the offset update
+            abl.dispatchOffsetUpdates(getTopAndBottomOffset());
 
             return handled;
         }
@@ -974,7 +1078,7 @@
                             ? interpolateOffset(appBarLayout, newOffset)
                             : newOffset;
 
-                    boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
+                    final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
 
                     // Update how much dy we have consumed
                     consumed = curOffset - newOffset;
@@ -990,7 +1094,11 @@
                     }
 
                     // Dispatch the updates to any listeners
-                    dispatchOffsetUpdates(appBarLayout);
+                    appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset());
+
+                    // Update the AppBarLayout's drawable state (for any elevation changes)
+                    updateAppBarLayoutDrawableState(appBarLayout, newOffset,
+                            newOffset < curOffset ? -1 : 1);
                 }
             } else {
                 // Reset the offset delta
@@ -1000,19 +1108,6 @@
             return consumed;
         }
 
-        private void dispatchOffsetUpdates(AppBarLayout layout) {
-            final List<OnOffsetChangedListener> listeners = layout.mListeners;
-
-            // Iterate backwards through the list so that most recently added listeners
-            // get the first chance to decide
-            for (int i = 0, z = listeners.size(); i < z; i++) {
-                final OnOffsetChangedListener listener = listeners.get(i);
-                if (listener != null) {
-                    listener.onOffsetChanged(layout, getTopAndBottomOffset());
-                }
-            }
-        }
-
         private int interpolateOffset(AppBarLayout layout, final int offset) {
             final int absOffset = Math.abs(offset);
 
@@ -1060,6 +1155,44 @@
             return offset;
         }
 
+        private void updateAppBarLayoutDrawableState(final AppBarLayout layout,
+                final int offset, final int direction) {
+            final View child = getAppBarChildOnOffset(layout, offset);
+            if (child != null) {
+                final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams();
+                final int flags = childLp.getScrollFlags();
+                boolean collapsed = false;
+
+                if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
+                    final int minHeight = ViewCompat.getMinimumHeight(child);
+
+                    if (direction > 0 && (flags & (LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
+                            | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED)) != 0) {
+                        // We're set to enter always collapsed so we are only collapsed when
+                        // being scrolled down, and in a collapsed offset
+                        collapsed = -offset >= child.getBottom() - minHeight - layout.getTopInset();
+                    } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
+                        // We're set to exit until collapsed, so any offset which results in
+                        // the minimum height (or less) being shown is collapsed
+                        collapsed = -offset >= child.getBottom() - minHeight - layout.getTopInset();
+                    }
+                }
+
+                layout.setCollapsed(collapsed);
+            }
+        }
+
+        private static View getAppBarChildOnOffset(final AppBarLayout layout, final int offset) {
+            final int absOffset = Math.abs(offset);
+            for (int i = 0, z = layout.getChildCount(); i < z; i++) {
+                final View child = layout.getChildAt(i);
+                if (absOffset >= child.getTop() && absOffset <= child.getBottom()) {
+                    return child;
+                }
+            }
+            return null;
+        }
+
         @Override
         int getTopBottomOffsetForScrollingSibling() {
             return getTopAndBottomOffset() + mOffsetDelta;
@@ -1182,7 +1315,7 @@
                 // any vertical gap, and overlap
                 final Behavior ablBehavior = (Behavior) behavior;
                 final int offset = ablBehavior.getTopBottomOffsetForScrollingSibling();
-                child.offsetTopAndBottom((dependency.getBottom() - child.getTop())
+                ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
                         + ablBehavior.mOffsetDelta
                         + getVerticalLayoutGap()
                         - getOverlapPixelsForOffset(dependency));
diff --git a/design/src/android/support/design/widget/CollapsingTextHelper.java b/design/src/android/support/design/widget/CollapsingTextHelper.java
index 6f8d51c..4a95a13 100644
--- a/design/src/android/support/design/widget/CollapsingTextHelper.java
+++ b/design/src/android/support/design/widget/CollapsingTextHelper.java
@@ -194,19 +194,26 @@
     }
 
     void setCollapsedTextAppearance(int resId) {
-        TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
-        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+        TypedArray a = mView.getContext().obtainStyledAttributes(resId,
+                android.support.v7.appcompat.R.styleable.TextAppearance);
+        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) {
             mCollapsedTextColor = a.getColor(
-                    R.styleable.TextAppearance_android_textColor, mCollapsedTextColor);
+                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor,
+                    mCollapsedTextColor);
         }
-        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
+        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) {
             mCollapsedTextSize = a.getDimensionPixelSize(
-                    R.styleable.TextAppearance_android_textSize, (int) mCollapsedTextSize);
+                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
+                    (int) mCollapsedTextSize);
         }
-        mCollapsedShadowColor = a.getInt(R.styleable.TextAppearance_android_shadowColor, 0);
-        mCollapsedShadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
-        mCollapsedShadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
-        mCollapsedShadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
+        mCollapsedShadowColor = a.getInt(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0);
+        mCollapsedShadowDx = a.getFloat(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
+        mCollapsedShadowDy = a.getFloat(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
+        mCollapsedShadowRadius = a.getFloat(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
         a.recycle();
 
         if (Build.VERSION.SDK_INT >= 16) {
@@ -217,19 +224,26 @@
     }
 
     void setExpandedTextAppearance(int resId) {
-        TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
-        if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+        TypedArray a = mView.getContext().obtainStyledAttributes(resId,
+                android.support.v7.appcompat.R.styleable.TextAppearance);
+        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) {
             mExpandedTextColor = a.getColor(
-                    R.styleable.TextAppearance_android_textColor, mExpandedTextColor);
+                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor,
+                    mExpandedTextColor);
         }
-        if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
+        if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) {
             mExpandedTextSize = a.getDimensionPixelSize(
-                    R.styleable.TextAppearance_android_textSize, (int) mExpandedTextSize);
+                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
+                    (int) mExpandedTextSize);
         }
-        mExpandedShadowColor = a.getInt(R.styleable.TextAppearance_android_shadowColor, 0);
-        mExpandedShadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
-        mExpandedShadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
-        mExpandedShadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
+        mExpandedShadowColor = a.getInt(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0);
+        mExpandedShadowDx = a.getFloat(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
+        mExpandedShadowDy = a.getFloat(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
+        mExpandedShadowRadius = a.getFloat(
+                android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
         a.recycle();
 
         if (Build.VERSION.SDK_INT >= 16) {
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 17fada5..04755ff 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -179,7 +179,7 @@
         mCollapsingTextHelper.setExpandedTextAppearance(
                 R.style.TextAppearance_Design_CollapsingToolbar_Expanded);
         mCollapsingTextHelper.setCollapsedTextAppearance(
-                R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
+                android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
 
         // Now overlay any custom text appearances
         if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) {
@@ -1117,7 +1117,6 @@
             mCurrentOffset = verticalOffset;
 
             final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
-            final int scrollRange = layout.getTotalScrollRange();
 
             for (int i = 0, z = getChildCount(); i < z; i++) {
                 final View child = getChildAt(i);
@@ -1151,15 +1150,6 @@
                     CollapsingToolbarLayout.this) - insetTop;
             mCollapsingTextHelper.setExpansionFraction(
                     Math.abs(verticalOffset) / (float) expandRange);
-
-            if (Math.abs(verticalOffset) == scrollRange) {
-                // If we have some pinned children, and we're offset to only show those views,
-                // we want to be elevate
-                ViewCompat.setElevation(layout, layout.getTargetElevation());
-            } else {
-                // Otherwise, we're inline with the content
-                ViewCompat.setElevation(layout, 0f);
-            }
         }
     }
 }
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 9f9c483..dfe44d6 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -148,6 +148,8 @@
     private final int[] mTempIntPair = new int[2];
     private Paint mScrimPaint;
 
+    private boolean mDisallowInterceptReset;
+
     private boolean mIsAttachedToWindow;
 
     private int[] mKeylines;
@@ -366,6 +368,7 @@
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             lp.resetTouchBehaviorTracking();
         }
+        mDisallowInterceptReset = false;
     }
 
     /**
@@ -530,8 +533,9 @@
     @Override
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         super.requestDisallowInterceptTouchEvent(disallowIntercept);
-        if (disallowIntercept) {
+        if (disallowIntercept && !mDisallowInterceptReset) {
             resetTouchBehaviors();
+            mDisallowInterceptReset = true;
         }
     }
 
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 0e31446..945b9b5 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -561,7 +561,7 @@
         public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
                 View dependency) {
             if (dependency instanceof Snackbar.SnackbarLayout) {
-                updateFabTranslationForSnackbar(parent, child, dependency);
+                updateFabTranslationForSnackbar(parent, child, true);
             } else if (dependency instanceof AppBarLayout) {
                 // If we're depending on an AppBarLayout we will show/hide it automatically
                 // if the FAB is anchored to the AppBarLayout
@@ -570,6 +570,14 @@
             return false;
         }
 
+        @Override
+        public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,
+                View dependency) {
+            if (dependency instanceof Snackbar.SnackbarLayout) {
+                updateFabTranslationForSnackbar(parent, child, true);
+            }
+        }
+
         private boolean updateFabVisibility(CoordinatorLayout parent,
                 AppBarLayout appBarLayout, FloatingActionButton child) {
             final CoordinatorLayout.LayoutParams lp =
@@ -604,7 +612,7 @@
         }
 
         private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
-                final FloatingActionButton fab, View snackbar) {
+                final FloatingActionButton fab, boolean animationAllowed) {
             final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
             if (mFabTranslationY == targetTransY) {
                 // We're already at (or currently animating to) the target value, return...
@@ -618,7 +626,7 @@
                 mFabTranslationYAnimator.cancel();
             }
 
-            if (fab.isShown()
+            if (animationAllowed && fab.isShown()
                     && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
                 // If the FAB will be travelling by more than 2/3 of its height, let's animate
                 // it instead
@@ -676,6 +684,8 @@
             parent.onLayoutChild(child, layoutDirection);
             // Now offset it if needed
             offsetIfNeeded(parent, child);
+            // Make sure we translate the FAB for any displayed Snackbars (without an animation)
+            updateFabTranslationForSnackbar(parent, child, false);
             return true;
         }
 
diff --git a/design/src/android/support/design/widget/NavigationView.java b/design/src/android/support/design/widget/NavigationView.java
index 696a7e4..05fdedb 100644
--- a/design/src/android/support/design/widget/NavigationView.java
+++ b/design/src/android/support/design/widget/NavigationView.java
@@ -413,7 +413,8 @@
             return null;
         }
         ColorStateList baseColor = getResources().getColorStateList(value.resourceId);
-        if (!getContext().getTheme().resolveAttribute(R.attr.colorPrimary, value, true)) {
+        if (!getContext().getTheme().resolveAttribute(
+                    android.support.v7.appcompat.R.attr.colorPrimary, value, true)) {
             return null;
         }
         int colorPrimary = value.data;
diff --git a/design/src/android/support/design/widget/SwipeDismissBehavior.java b/design/src/android/support/design/widget/SwipeDismissBehavior.java
index 5e4837f..bfc98ea 100644
--- a/design/src/android/support/design/widget/SwipeDismissBehavior.java
+++ b/design/src/android/support/design/widget/SwipeDismissBehavior.java
@@ -211,15 +211,20 @@
     }
 
     private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {
+        private static final int INVALID_POINTER_ID = -1;
+
         private int mOriginalCapturedViewLeft;
+        private int mActivePointerId = INVALID_POINTER_ID;
 
         @Override
         public boolean tryCaptureView(View child, int pointerId) {
-            return canSwipeDismissView(child);
+            // Only capture if we don't already have an active pointer id
+            return mActivePointerId == INVALID_POINTER_ID && canSwipeDismissView(child);
         }
 
         @Override
         public void onViewCaptured(View capturedChild, int activePointerId) {
+            mActivePointerId = activePointerId;
             mOriginalCapturedViewLeft = capturedChild.getLeft();
 
             // The view has been captured, and thus a drag is about to start so stop any parents
@@ -239,6 +244,9 @@
 
         @Override
         public void onViewReleased(View child, float xvel, float yvel) {
+            // Reset the active pointer ID
+            mActivePointerId = INVALID_POINTER_ID;
+
             final int childWidth = child.getWidth();
             int targetLeft;
             boolean dismiss = false;
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 1059b9a..9922c94 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -299,10 +299,12 @@
 
         // Text colors/sizes come from the text appearance first
         final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,
-                R.styleable.TextAppearance);
+                android.support.v7.appcompat.R.styleable.TextAppearance);
         try {
-            mTabTextSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, 0);
-            mTabTextColors = ta.getColorStateList(R.styleable.TextAppearance_android_textColor);
+            mTabTextSize = ta.getDimensionPixelSize(
+                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0);
+            mTabTextColors = ta.getColorStateList(
+                    android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
         } finally {
             ta.recycle();
         }
@@ -1537,10 +1539,11 @@
                         // If we're in fixed mode, going up in text size and currently have 1 line
                         // then it's very easy to get into an infinite recursion.
                         // To combat that we check to see if the change in text size
-                        // will cause a line count change. If so, abort the size change.
+                        // will cause a line count change. If so, abort the size change and stick
+                        // to the smaller size.
                         final Layout layout = mTextView.getLayout();
-                        if (layout == null
-                                || approximateLineWidth(layout, 0, textSize) > layout.getWidth()) {
+                        if (layout == null || approximateLineWidth(layout, 0, textSize)
+                                > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) {
                             updateTextView = false;
                         }
                     }
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index ffd7a83..a38439f 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -494,7 +494,7 @@
                     // Probably caused by our theme not extending from Theme.Design*. Instead
                     // we manually set something appropriate
                     mErrorView.setTextAppearance(getContext(),
-                            R.style.TextAppearance_AppCompat_Caption);
+                            android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption);
                     mErrorView.setTextColor(ContextCompat.getColor(
                             getContext(), R.color.design_textinput_error_color_light));
                 }
@@ -615,7 +615,7 @@
                     // Probably caused by our theme not extending from Theme.Design*. Instead
                     // we manually set something appropriate
                     mCounterView.setTextAppearance(getContext(),
-                            R.style.TextAppearance_AppCompat_Caption);
+                            android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption);
                     mCounterView.setTextColor(ContextCompat.getColor(
                             getContext(), R.color.design_textinput_error_color_light));
                 }
@@ -725,7 +725,7 @@
 
     private void ensureBackgroundDrawableStateWorkaround() {
         final int sdk = Build.VERSION.SDK_INT;
-        if (sdk != 21 || sdk != 22) {
+        if (sdk != 21 && sdk != 22) {
             // The workaround is only required on API 21-22
             return;
         }
diff --git a/design/src/android/support/design/widget/ThemeUtils.java b/design/src/android/support/design/widget/ThemeUtils.java
index 327a44d..ffdc3f4 100644
--- a/design/src/android/support/design/widget/ThemeUtils.java
+++ b/design/src/android/support/design/widget/ThemeUtils.java
@@ -22,7 +22,9 @@
 
 class ThemeUtils {
 
-    private static final int[] APPCOMPAT_CHECK_ATTRS = { R.attr.colorPrimary };
+    private static final int[] APPCOMPAT_CHECK_ATTRS = {
+            android.support.v7.appcompat.R.attr.colorPrimary
+    };
 
     static void checkAppCompatTheme(Context context) {
         TypedArray a = context.obtainStyledAttributes(APPCOMPAT_CHECK_ATTRS);
diff --git a/design/src/android/support/design/widget/ViewOffsetHelper.java b/design/src/android/support/design/widget/ViewOffsetHelper.java
index 1254f17..fc16d28 100644
--- a/design/src/android/support/design/widget/ViewOffsetHelper.java
+++ b/design/src/android/support/design/widget/ViewOffsetHelper.java
@@ -16,10 +16,8 @@
 
 package android.support.design.widget;
 
-import android.os.Build;
 import android.support.v4.view.ViewCompat;
 import android.view.View;
-import android.view.ViewParent;
 
 /**
  * Utility helper for moving a {@link android.view.View} around using
@@ -54,21 +52,6 @@
     private void updateOffsets() {
         ViewCompat.offsetTopAndBottom(mView, mOffsetTop - (mView.getTop() - mLayoutTop));
         ViewCompat.offsetLeftAndRight(mView, mOffsetLeft - (mView.getLeft() - mLayoutLeft));
-
-        // Manually invalidate the view and parent to make sure we get drawn pre-M
-        if (Build.VERSION.SDK_INT < 23) {
-            tickleInvalidationFlag(mView);
-            final ViewParent vp = mView.getParent();
-            if (vp instanceof View) {
-                tickleInvalidationFlag((View) vp);
-            }
-        }
-    }
-
-    private static void tickleInvalidationFlag(View view) {
-        final float y = ViewCompat.getTranslationY(view);
-        ViewCompat.setTranslationY(view, y + 1);
-        ViewCompat.setTranslationY(view, y);
     }
 
     /**
diff --git a/design/src/android/support/design/widget/ViewUtils.java b/design/src/android/support/design/widget/ViewUtils.java
index 29a4522..c27716c 100644
--- a/design/src/android/support/design/widget/ViewUtils.java
+++ b/design/src/android/support/design/widget/ViewUtils.java
@@ -17,7 +17,6 @@
 package android.support.design.widget;
 
 import android.os.Build;
-import android.view.View;
 
 class ViewUtils {
 
@@ -31,39 +30,6 @@
         }
     };
 
-    private interface ViewUtilsImpl {
-        void setBoundsViewOutlineProvider(View view);
-    }
-
-    private static class ViewUtilsImplBase implements ViewUtilsImpl {
-        @Override
-        public void setBoundsViewOutlineProvider(View view) {
-            // no-op
-        }
-    }
-
-    private static class ViewUtilsImplLollipop implements ViewUtilsImpl {
-        @Override
-        public void setBoundsViewOutlineProvider(View view) {
-            ViewUtilsLollipop.setBoundsViewOutlineProvider(view);
-        }
-    }
-
-    private static final ViewUtilsImpl IMPL;
-
-    static {
-        final int version = Build.VERSION.SDK_INT;
-        if (version >= 21) {
-            IMPL = new ViewUtilsImplLollipop();
-        } else {
-            IMPL = new ViewUtilsImplBase();
-        }
-    }
-
-    static void setBoundsViewOutlineProvider(View view) {
-        IMPL.setBoundsViewOutlineProvider(view);
-    }
-
     static ValueAnimatorCompat createAnimator() {
         return DEFAULT_ANIMATOR_CREATOR.createAnimator();
     }
diff --git a/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
index 793f6d9..0e73c41 100644
--- a/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
@@ -16,6 +16,7 @@
 
 package android.support.design.widget;
 
+import android.os.Build;
 import android.support.annotation.CallSuper;
 import android.support.annotation.IdRes;
 import android.support.annotation.LayoutRes;
@@ -27,6 +28,7 @@
 import android.support.test.espresso.action.GeneralSwipeAction;
 import android.support.test.espresso.action.Press;
 import android.support.test.espresso.action.Swipe;
+import android.support.v4.view.ViewCompat;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
 import android.text.TextUtils;
@@ -38,6 +40,8 @@
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
+import static org.junit.Assert.assertEquals;
+
 public abstract class AppBarLayoutBaseTest extends BaseDynamicCoordinatorLayoutTest {
 
     protected AppBarLayout mAppBar;
@@ -48,6 +52,8 @@
 
     protected TextView mTextView;
 
+    protected float mDefaultElevationValue;
+
     protected static void performVerticalSwipeUpGesture(@IdRes int containerId, final int swipeX,
             final int swipeStartY, final int swipeAmountY) {
         onView(withId(containerId)).perform(new GeneralSwipeAction(
@@ -113,5 +119,14 @@
             onView(withId(R.id.textview_dialogue)).perform(
                     setText(TextUtils.concat(Shakespeare.DIALOGUE)));
         }
+
+        mDefaultElevationValue = mAppBar.getResources()
+                .getDimensionPixelSize(R.dimen.design_appbar_elevation);
+    }
+
+    protected void assertAppBarElevation(float expectedValue) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            assertEquals(expectedValue, ViewCompat.getElevation(mAppBar), 0.05f);
+        }
     }
 }
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
index 737668b..249d891 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
@@ -51,6 +51,8 @@
         final int longSwipeAmount = 3 * appbarHeight / 2;
         final int shortSwipeAmount = toolbarHeight;
 
+        assertAppBarElevation(0f);
+
         // Perform a swipe-up gesture across the horizontal center of the screen.
         performVerticalSwipeUpGesture(
                 R.id.coordinator_layout,
@@ -74,6 +76,7 @@
         // At this point the app bar should still be visually snapped below the system status bar
         // as it is in the pinned mode. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a short swipe-down gesture across the horizontal center of the screen.
         // Note that the swipe down is a bit longer than the swipe up to check that the app bar
@@ -89,6 +92,7 @@
         // as it is in the pinned mode and we haven't fully swiped down the content below the
         // app bar. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -102,6 +106,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(0f);
 
         // Perform yet another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -115,6 +120,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(0f);
     }
 
     @Test
@@ -141,6 +147,8 @@
         final int longSwipeAmount = 3 * appbarHeight / 2;
         final int shortSwipeAmount = toolbarHeight;
 
+        assertAppBarElevation(0f);
+
         // Perform a swipe-up gesture across the horizontal center of the screen.
         performVerticalSwipeUpGesture(
                 R.id.coordinator_layout,
@@ -153,6 +161,7 @@
         // edge aligned with the system status bar.
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(0f);
 
         // Perform another swipe-up gesture
         performVerticalSwipeUpGesture(
@@ -165,6 +174,7 @@
         // At this point the app bar should still be off the screen. Allow for off-by-a-pixel
         // margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(0f);
 
         // Perform a short swipe-down gesture across the horizontal center of the screen.
         // Note that the swipe down is a bit longer than the swipe up to fully bring down
@@ -180,6 +190,7 @@
         // in scrolling mode and we've swiped down, but not fully. Allow for off-by-a-pixel
         // margin of error.
         assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -193,6 +204,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1]);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight);
+        assertAppBarElevation(0f);
 
         // Perform yet another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -206,6 +218,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(0f);
     }
 
     @Test
diff --git a/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
index d3529cc..0c715de 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
@@ -76,6 +76,7 @@
         // At this point the app bar should not be visually "present" on the screen, with its bottom
         // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform another swipe-up gesture
         performVerticalSwipeUpGesture(
@@ -88,6 +89,7 @@
         // At this point the app bar should still be off the screen. Allow for off-by-a-pixel
         // margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a long swipe-down gesture across the horizontal center of the screen.
         // Note that the swipe down is a bit longer than the swipe up to fully bring down
@@ -104,6 +106,7 @@
         // margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -117,6 +120,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform yet another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -130,6 +134,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
     }
 
     @Test
@@ -163,6 +168,7 @@
         // At this point the tab bar should be visually snapped below the system status bar.
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform another swipe-up gesture
         performVerticalSwipeUpGesture(
@@ -175,6 +181,7 @@
         // At this point the tab bar should still be visually snapped below the system status bar
         // as it is in the pinned mode. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a short swipe-down gesture across the horizontal center of the screen.
         // Note that the swipe down is a bit longer than the swipe up to fully bring down
@@ -191,6 +198,7 @@
         // margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -204,6 +212,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform yet another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -217,6 +226,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
     }
 
     @LargeTest
@@ -264,6 +274,7 @@
         // margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a slightly longer swipe-up gesture, this time by 75% of the toolbar height.
         // We expect the snap behavior to move the app bar to snap the tab layout below the
@@ -281,6 +292,7 @@
         // At this point the app bar should "snap" the toolbar away and align the tab layout below
         // the system status bar. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a short swipe-up gesture, this time by 25% of the tab layout height. We expect
         // snap behavior to move the app bar back to snap the tab layout below the system status
@@ -298,6 +310,7 @@
         // At this point the app bar should "snap" back to align the tab layout below
         // the system status bar. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect
         // snap behavior to move the app bar fully away from the screen.
@@ -314,6 +327,7 @@
         // At this point the app bar should not be visually "present" on the screen, with its bottom
         // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a short swipe-down gesture by 25% of the tab layout height. We expect
         // snap behavior to move the app bar back fully away from the screen.
@@ -331,6 +345,7 @@
         // its bottom edge aligned with the system status bar. Allow for off-by-a-pixel margin
         // of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect
         // snap behavior to move the app bar to snap the tab layout below the system status
@@ -348,6 +363,7 @@
         // At this point the app bar should "snap" the toolbar away and align the tab layout below
         // the system status bar. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a short swipe-down gesture by 25% of the toolbar height. We expect
         // snap behavior to align the tab layout below the system status bar
@@ -364,6 +380,7 @@
         // At this point the app bar should still align the tab layout below
         // the system status bar. Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
 
         // Perform a longer swipe-up gesture, this time by 75% of the toolbar height. We expect
         // snap behavior to move the app bar back to its original place (fully visible).
@@ -381,5 +398,6 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+        assertAppBarElevation(mDefaultElevationValue);
     }
 }
diff --git a/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java b/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
index 3b06ae2..ca56ecc 100644
--- a/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
+++ b/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
@@ -30,6 +30,7 @@
 import org.junit.After;
 import org.junit.Test;
 
+import static android.support.design.widget.DesignViewActions.setVisibility;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
@@ -115,6 +116,26 @@
     }
 
     @Test
+    public void testBuiltInSlidingFromHiddenFab() {
+        onView(withId(R.id.coordinator_stub)).perform(
+                inflateViewStub(R.layout.design_snackbar_with_fab));
+        onView(withId(R.id.fab)).perform(setVisibility(View.GONE));
+
+        // Create and show a snackbar
+        mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+                .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+        SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+        // Take into account bottom padding and bottom margin to account for how drop shadow is
+        // emulated on pre-Lollipop devices
+        onView(withId(R.id.fab)).perform(setVisibility(View.VISIBLE));
+        final FloatingActionButton fab =
+                (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+        verifySnackbarViewStacking(fab, fab.getPaddingBottom()
+                - ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin);
+    }
+
+    @Test
     public void testBehaviorBasedSlidingFromLayoutAttribute() {
         // Use a layout in which an AppCompatTextView child has Behavior object configured via
         // layout_behavior XML attribute
diff --git a/design/tests/src/android/support/design/widget/DesignViewActions.java b/design/tests/src/android/support/design/widget/DesignViewActions.java
index ad9e294..4ae608a 100644
--- a/design/tests/src/android/support/design/widget/DesignViewActions.java
+++ b/design/tests/src/android/support/design/widget/DesignViewActions.java
@@ -20,6 +20,7 @@
 
 import android.support.test.espresso.UiController;
 import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.matcher.ViewMatchers;
 import android.view.View;
 
 public final class DesignViewActions {
@@ -50,4 +51,25 @@
         };
     }
 
+    public static ViewAction setVisibility(final int visibility) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return ViewMatchers.isEnabled();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set view visibility";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+                view.setVisibility(visibility);
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
 }
diff --git a/documents-archive/Android.mk b/documents-archive/Android.mk
index 44c9f66..32ec7d6 100644
--- a/documents-archive/Android.mk
+++ b/documents-archive/Android.mk
@@ -15,15 +15,25 @@
 LOCAL_PATH := $(call my-dir)
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-documents-archive \
+#       android-support-v4 \
+#       android-support-annotations
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-documents-archive
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_AIDL_INCLUDES := $LOCAL_PATH/src
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android-support-annotations \
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-annotations \
     android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/documents-archive/src/android/support/provider/DocumentArchive.java b/documents-archive/src/android/support/provider/DocumentArchive.java
index dc9a7b3..5db5b38 100644
--- a/documents-archive/src/android/support/provider/DocumentArchive.java
+++ b/documents-archive/src/android/support/provider/DocumentArchive.java
@@ -21,10 +21,13 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.graphics.Point;
+import android.media.ExifInterface;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsProvider;
 import android.support.annotation.Nullable;
@@ -439,12 +442,42 @@
         Preconditions.checkArgument(getDocumentType(documentId).startsWith("image/"),
                 "Thumbnails only supported for image/* MIME type.");
 
-        // TODO: Extract thumbnails from EXIF.
         final ZipEntry entry = mEntries.get(parsedId.mPath);
         if (entry == null) {
             throw new FileNotFoundException();
         }
 
+        InputStream inputStream = null;
+        try {
+            inputStream = mZipFile.getInputStream(entry);
+            final ExifInterface exif = new ExifInterface(inputStream);
+            if (exif.hasThumbnail()) {
+                Bundle extras = null;
+                switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
+                    case ExifInterface.ORIENTATION_ROTATE_90:
+                        extras = new Bundle(1);
+                        extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 90);
+                        break;
+                    case ExifInterface.ORIENTATION_ROTATE_180:
+                        extras = new Bundle(1);
+                        extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 180);
+                        break;
+                    case ExifInterface.ORIENTATION_ROTATE_270:
+                        extras = new Bundle(1);
+                        extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 270);
+                        break;
+                }
+                final long[] range = exif.getThumbnailRange();
+                return new AssetFileDescriptor(
+                        openDocument(documentId, "r", signal), range[0], range[1], extras);
+            }
+        } catch (IOException e) {
+            // Ignore the exception, as reading the EXIF may legally fail.
+            Log.e(TAG, "Failed to obtain thumbnail from EXIF.", e);
+        } finally {
+            IoUtils.closeQuietly(inputStream);
+        }
+
         return new AssetFileDescriptor(
                 openDocument(documentId, "r", signal), 0, entry.getSize(), null);
     }
diff --git a/percent/Android.mk b/percent/Android.mk
index 6e04560..59556c0 100644
--- a/percent/Android.mk
+++ b/percent/Android.mk
@@ -14,30 +14,24 @@
 
 LOCAL_PATH := $(call my-dir)
 
-# Build the resources using the latest applicable SDK version.
-# We do this here because the final static library must be compiled with an older
-# SDK version than the resources.  The resources library and the R class that it
-# contains will not be linked into the final static library.
-include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-percent-res
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_JAR_EXCLUDE_FILES := none
-LOCAL_JAVA_LANGUAGE_VERSION := 1.7
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must include it with
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-percent \
+#       android-support-v4
+#
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-percent
 LOCAL_SDK_VERSION := 8
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android-support-percent-res \
-    android-support-v4
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/percent/src/android/support/percent/PercentLayoutHelper.java b/percent/src/android/support/percent/PercentLayoutHelper.java
index 2416f88..63afaec 100644
--- a/percent/src/android/support/percent/PercentLayoutHelper.java
+++ b/percent/src/android/support/percent/PercentLayoutHelper.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.support.annotation.NonNull;
 import android.support.v4.view.MarginLayoutParamsCompat;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
@@ -77,7 +78,10 @@
 
     private final ViewGroup mHost;
 
-    public PercentLayoutHelper(ViewGroup host) {
+    public PercentLayoutHelper(@NonNull ViewGroup host) {
+        if (host == null) {
+            throw new IllegalArgumentException("host must be non-null");
+        }
         mHost = host;
     }
 
@@ -105,8 +109,11 @@
                     + View.MeasureSpec.toString(heightMeasureSpec));
         }
 
-        int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);
-        int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);
+        // Calculate available space, accounting for host's paddings
+        int widthHint = View.MeasureSpec.getSize(widthMeasureSpec) - mHost.getPaddingLeft()
+                - mHost.getPaddingRight();
+        int heightHint = View.MeasureSpec.getSize(heightMeasureSpec) - mHost.getPaddingTop()
+                - mHost.getPaddingBottom();
         for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
             View view = mHost.getChildAt(i);
             ViewGroup.LayoutParams params = view.getLayoutParams();
@@ -341,22 +348,31 @@
      * for {@code LayoutParams}.
      */
     public static class PercentLayoutInfo {
+        /** The decimal value of the percentage-based width. */
         public float widthPercent;
 
+        /** The decimal value of the percentage-based height. */
         public float heightPercent;
 
+        /** The decimal value of the percentage-based left margin. */
         public float leftMarginPercent;
 
+        /** The decimal value of the percentage-based top margin. */
         public float topMarginPercent;
 
+        /** The decimal value of the percentage-based right margin. */
         public float rightMarginPercent;
 
+        /** The decimal value of the percentage-based bottom margin. */
         public float bottomMarginPercent;
 
+        /** The decimal value of the percentage-based start margin. */
         public float startMarginPercent;
 
+        /** The decimal value of the percentage-based end margin. */
         public float endMarginPercent;
 
+        /** The decimal value of the percentage-based aspect ratio. */
         public float aspectRatio;
 
         /* package */ final PercentMarginLayoutParams mPreservedParams;
@@ -374,7 +390,9 @@
         }
 
         /**
-         * Fills {@code ViewGroup.LayoutParams} dimensions based on percentage values.
+         * Fills the {@link ViewGroup.LayoutParams#width} and {@link ViewGroup.LayoutParams#height}
+         * fields of the passed {@link ViewGroup.LayoutParams} object based on currently set
+         * percentage values.
          */
         public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
                 int heightHint) {
@@ -431,8 +449,9 @@
         }
 
         /**
-         * Fills {@code ViewGroup.MarginLayoutParams} dimensions and margins based on percentage
-         * values.
+         * Fills the margin fields of the passed {@link ViewGroup.MarginLayoutParams} object based
+         * on currently set percentage values and the current layout direction of the passed
+         * {@link View}.
          */
         public void fillMarginLayoutParams(View view, ViewGroup.MarginLayoutParams params,
                 int widthHint, int heightHint) {
@@ -493,9 +512,9 @@
         }
 
         /**
-         * Restores original dimensions and margins after they were changed for percentage based
-         * values. Calling this method only makes sense if you previously called
-         * {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams}.
+         * Restores the original dimensions and margins after they were changed for percentage based
+         * values. You should call this method only if you previously called
+         * {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)}.
          */
         public void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params) {
             restoreLayoutParams(params);
@@ -510,9 +529,9 @@
         }
 
         /**
-         * Restores original dimensions after they were changed for percentage based values. Calling
-         * this method only makes sense if you previously called
-         * {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams}.
+         * Restores original dimensions after they were changed for percentage based values.
+         * You should call this method only if you previously called
+         * {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams(ViewGroup.LayoutParams, int, int)}.
          */
         public void restoreLayoutParams(ViewGroup.LayoutParams params) {
             if (!mPreservedParams.mIsWidthComputedFromAspectRatio) {
diff --git a/percent/tests/AndroidManifest.xml b/percent/tests/AndroidManifest.xml
index c4cc597..993e69b 100644
--- a/percent/tests/AndroidManifest.xml
+++ b/percent/tests/AndroidManifest.xml
@@ -29,6 +29,7 @@
         <activity android:name="android.support.percent.TestFrameActivity"/>
         <activity android:name="android.support.percent.TestRelativeActivity"/>
         <activity android:name="android.support.percent.TestRelativeRtlActivity"/>
+        <activity android:name="android.support.percent.PercentDynamicLayoutActivity"/>
     </application>
 
     <instrumentation
diff --git a/percent/tests/java/android/support/percent/PercentDynamicLayoutActivity.java b/percent/tests/java/android/support/percent/PercentDynamicLayoutActivity.java
new file mode 100644
index 0000000..6490123
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentDynamicLayoutActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.percent;
+
+import android.support.percent.test.R;
+
+/**
+ * Test activity for testing presence of single and multiple drawers in percent layouts.
+ */
+public class PercentDynamicLayoutActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.percent_dynamic_layout;
+    }
+}
diff --git a/percent/tests/java/android/support/percent/PercentDynamicLayoutTest.java b/percent/tests/java/android/support/percent/PercentDynamicLayoutTest.java
new file mode 100755
index 0000000..08a96bc
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentDynamicLayoutTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.percent;
+
+import android.support.annotation.LayoutRes;
+import android.support.percent.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.view.ViewStub;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.core.AllOf.allOf;
+
+/**
+ * Test cases to verify that percent layouts properly account for their own paddings.
+ */
+@SmallTest
+public class PercentDynamicLayoutTest
+        extends BaseInstrumentationTestCase<PercentDynamicLayoutActivity> {
+    public PercentDynamicLayoutTest() {
+        super(PercentDynamicLayoutActivity.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Now that the test is done, replace the activity content view with ViewStub so
+        // that it's ready to be replaced for the next test.
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final PercentDynamicLayoutActivity activity = mActivityTestRule.getActivity();
+                activity.setContentView(R.layout.percent_dynamic_layout);
+            }
+        });
+    }
+
+    /**
+     * Matches views that have parents.
+     */
+    private Matcher<View> hasParent() {
+        return new TypeSafeMatcher<View>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("has parent");
+            }
+
+            @Override
+            public boolean matchesSafely(View view) {
+                return view.getParent() != null;
+            }
+        };
+    }
+
+    /**
+     * Inflates the <code>ViewStub</code> with the passed layout resource.
+     */
+    private ViewAction inflateViewStub(final @LayoutRes int layoutResId) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return allOf(isAssignableFrom(ViewStub.class), hasParent());
+            }
+
+            @Override
+            public String getDescription() {
+                return "Inflates view stub";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                ViewStub viewStub = (ViewStub) view;
+                viewStub.setLayoutResource(layoutResId);
+                viewStub.inflate();
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
+    @Test
+    public void testPercentFrameWithHorizontalPaddings() {
+        onView(withId(R.id.percent_layout)).check(doesNotExist());
+        onView(withId(R.id.percent_stub)).perform(
+                inflateViewStub(R.layout.percent_frame_layout_hpaddings));
+
+        final PercentFrameLayout percentFrameLayout =
+                (PercentFrameLayout) mActivityTestRule.getActivity().findViewById(
+                        R.id.percent_layout);
+        final int containerWidth = percentFrameLayout.getWidth();
+        final int containerHeight = percentFrameLayout.getHeight();
+
+        final int availableWidth = containerWidth - percentFrameLayout.getPaddingLeft()
+                - percentFrameLayout.getPaddingRight();
+        final int availableHeight = containerHeight - percentFrameLayout.getPaddingTop()
+                - percentFrameLayout.getPaddingBottom();
+
+        final View child1 = percentFrameLayout.findViewById(R.id.child1);
+        final View child2 = percentFrameLayout.findViewById(R.id.child2);
+
+        assertFuzzyEquals("Child 1 width as 50% of the container's available width",
+                0.5f * availableWidth, child1.getWidth());
+        assertFuzzyEquals("Child 1 height as 100% of the container's available height",
+                availableHeight, child1.getHeight());
+        assertFuzzyEquals("Child 2 width as 50% of the container's available width",
+                0.5f * availableWidth, child2.getWidth());
+        assertFuzzyEquals("Child 2 height as 100% of the container's available height",
+                availableHeight, child2.getHeight());
+    }
+
+    @Test
+    public void testPercentFrameWithVerticalPaddings() {
+        onView(withId(R.id.percent_layout)).check(doesNotExist());
+        onView(withId(R.id.percent_stub)).perform(
+                inflateViewStub(R.layout.percent_frame_layout_vpaddings));
+
+        final PercentFrameLayout percentFrameLayout =
+                (PercentFrameLayout) mActivityTestRule.getActivity().findViewById(
+                        R.id.percent_layout);
+        final int containerWidth = percentFrameLayout.getWidth();
+        final int containerHeight = percentFrameLayout.getHeight();
+
+        final int availableWidth = containerWidth - percentFrameLayout.getPaddingLeft()
+                - percentFrameLayout.getPaddingRight();
+        final int availableHeight = containerHeight - percentFrameLayout.getPaddingTop()
+                - percentFrameLayout.getPaddingBottom();
+
+        final View child1 = percentFrameLayout.findViewById(R.id.child1);
+        final View child2 = percentFrameLayout.findViewById(R.id.child2);
+
+        assertFuzzyEquals("Child 1 width as 100% of the container's available width",
+                availableWidth, child1.getWidth());
+        assertFuzzyEquals("Child 1 height as 50% of the container's available height",
+                0.5f * availableHeight, child1.getHeight());
+        assertFuzzyEquals("Child 2 width as 100% of the container's available width",
+                availableWidth, child2.getWidth());
+        assertFuzzyEquals("Child 2 height as 50% of the container's available height",
+                0.5f* availableHeight, child2.getHeight());
+    }
+
+    @Test
+    public void testPercentRelativeWithHorizontalPaddings() {
+        onView(withId(R.id.percent_layout)).check(doesNotExist());
+        onView(withId(R.id.percent_stub)).perform(
+                inflateViewStub(R.layout.percent_relative_layout_hpaddings));
+
+        final PercentRelativeLayout percentRelativeLayout =
+                (PercentRelativeLayout) mActivityTestRule.getActivity().findViewById(
+                        R.id.percent_layout);
+        final int containerWidth = percentRelativeLayout.getWidth();
+        final int containerHeight = percentRelativeLayout.getHeight();
+
+        final int availableWidth = containerWidth - percentRelativeLayout.getPaddingLeft()
+                - percentRelativeLayout.getPaddingRight();
+        final int availableHeight = containerHeight - percentRelativeLayout.getPaddingTop()
+                - percentRelativeLayout.getPaddingBottom();
+
+        final View child1 = percentRelativeLayout.findViewById(R.id.child1);
+        final View child2 = percentRelativeLayout.findViewById(R.id.child2);
+
+        assertFuzzyEquals("Child 1 width as 50% of the container's available width",
+                0.5f * availableWidth, child1.getWidth());
+        assertFuzzyEquals("Child 1 height as 100% of the container's available height",
+                availableHeight, child1.getHeight());
+        assertFuzzyEquals("Child 2 width as 50% of the container's available width",
+                0.5f * availableWidth, child2.getWidth());
+        assertFuzzyEquals("Child 2 height as 100% of the container's available height",
+                availableHeight, child2.getHeight());
+    }
+
+    @Test
+    public void testPercentRelaticeWithVerticalPaddings() {
+        onView(withId(R.id.percent_layout)).check(doesNotExist());
+        onView(withId(R.id.percent_stub)).perform(
+                inflateViewStub(R.layout.percent_relative_layout_vpaddings));
+
+        final PercentRelativeLayout percentRelativeLayout =
+                (PercentRelativeLayout) mActivityTestRule.getActivity().findViewById(
+                        R.id.percent_layout);
+        final int containerWidth = percentRelativeLayout.getWidth();
+        final int containerHeight = percentRelativeLayout.getHeight();
+
+        final int availableWidth = containerWidth - percentRelativeLayout.getPaddingLeft()
+                - percentRelativeLayout.getPaddingRight();
+        final int availableHeight = containerHeight - percentRelativeLayout.getPaddingTop()
+                - percentRelativeLayout.getPaddingBottom();
+
+        final View child1 = percentRelativeLayout.findViewById(R.id.child1);
+        final View child2 = percentRelativeLayout.findViewById(R.id.child2);
+
+        assertFuzzyEquals("Child 1 width as 100% of the container's available width",
+                availableWidth, child1.getWidth());
+        assertFuzzyEquals("Child 1 height as 50% of the container's available height",
+                0.5f * availableHeight, child1.getHeight());
+        assertFuzzyEquals("Child 2 width as 100% of the container's available width",
+                availableWidth, child2.getWidth());
+        assertFuzzyEquals("Child 2 height as 50% of the container's available height",
+                0.5f* availableHeight, child2.getHeight());
+    }
+}
diff --git a/percent/tests/java/android/support/percent/PercentFrameTest.java b/percent/tests/java/android/support/percent/PercentFrameTest.java
index 67d2ae5..8b2fe8b 100644
--- a/percent/tests/java/android/support/percent/PercentFrameTest.java
+++ b/percent/tests/java/android/support/percent/PercentFrameTest.java
@@ -50,7 +50,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testWidthHeight() {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_width_height);
 
@@ -64,7 +63,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testWidthAspectRatio() {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_width_ratio);
 
@@ -78,7 +76,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testHeightAspectRatio() {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_height_ratio);
 
@@ -92,7 +89,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMarginsSingle() {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_single);
 
@@ -112,7 +108,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMarginsMultiple() {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_multiple);
 
@@ -132,7 +127,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMarginsTopLeft() {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_top_left);
 
@@ -152,7 +146,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMarginsBottomRight() {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_bottom_right);
 
@@ -172,7 +165,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMarginStart() throws Throwable {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_start);
 
@@ -183,7 +175,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMarginStartRtl() throws Throwable {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_start);
 
@@ -216,7 +207,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMarginEnd() throws Throwable {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_end);
 
@@ -227,7 +217,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testMarginEndRtl() throws Throwable {
         View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_end);
 
diff --git a/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java b/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
index 634637c..2cb679d 100644
--- a/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
+++ b/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
@@ -120,7 +120,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testTopChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_top);
 
@@ -150,7 +149,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testStartChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_start);
 
@@ -181,7 +179,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testBottomChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
 
@@ -213,7 +210,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testEndChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_end);
 
@@ -244,7 +240,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testCenterChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
 
diff --git a/percent/tests/java/android/support/percent/PercentRelativeTest.java b/percent/tests/java/android/support/percent/PercentRelativeTest.java
index 31d406e..eaa38f6 100644
--- a/percent/tests/java/android/support/percent/PercentRelativeTest.java
+++ b/percent/tests/java/android/support/percent/PercentRelativeTest.java
@@ -73,7 +73,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testTopChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_top);
 
@@ -95,7 +94,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testLeftChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_left);
 
@@ -117,7 +115,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testBottomChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
 
@@ -139,7 +136,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testRightChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_right);
 
@@ -161,7 +157,6 @@
     }
 
     @Test
-    @UiThreadTest
     public void testCenterChild() {
         final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
 
diff --git a/percent/tests/res/layout/percent_dynamic_layout.xml b/percent/tests/res/layout/percent_dynamic_layout.xml
new file mode 100644
index 0000000..3ecdccf
--- /dev/null
+++ b/percent/tests/res/layout/percent_dynamic_layout.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<!-- ViewStub that will be inflated to a PercentLayout at test runtime. -->
+<ViewStub
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/percent_stub"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:inflatedId="@+id/percent_layout"
+    android:fitsSystemWindows="true" />
diff --git a/percent/tests/res/layout/percent_frame_layout_hpaddings.xml b/percent/tests/res/layout/percent_frame_layout_hpaddings.xml
new file mode 100644
index 0000000..5cc476d
--- /dev/null
+++ b/percent/tests/res/layout/percent_frame_layout_hpaddings.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<android.support.percent.PercentFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="48dip"
+    android:paddingRight="64dip">
+    <View
+        android:id="@+id/child1"
+        app:layout_widthPercent="50%"
+        android:layout_height="match_parent"
+        android:layout_gravity="left"
+        android:background="#80FF0000" />
+    <View
+        android:id="@+id/child2"
+        app:layout_widthPercent="50%"
+        android:layout_height="match_parent"
+        android:layout_gravity="right"
+        android:background="#800000FF" />
+</android.support.percent.PercentFrameLayout>
diff --git a/percent/tests/res/layout/percent_frame_layout_vpaddings.xml b/percent/tests/res/layout/percent_frame_layout_vpaddings.xml
new file mode 100644
index 0000000..d71c368
--- /dev/null
+++ b/percent/tests/res/layout/percent_frame_layout_vpaddings.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<android.support.percent.PercentFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="48dip"
+    android:paddingBottom="64dip">
+    <View
+        android:id="@+id/child1"
+        android:layout_width="match_parent"
+        app:layout_heightPercent="50%"
+        android:layout_gravity="top"
+        android:background="#80FF0000" />
+    <View
+        android:id="@+id/child2"
+        android:layout_width="match_parent"
+        app:layout_heightPercent="50%"
+        android:layout_gravity="bottom"
+        android:background="#800000FF" />
+</android.support.percent.PercentFrameLayout>
diff --git a/percent/tests/res/layout/percent_relative_layout_hpaddings.xml b/percent/tests/res/layout/percent_relative_layout_hpaddings.xml
new file mode 100644
index 0000000..2d6f1e0
--- /dev/null
+++ b/percent/tests/res/layout/percent_relative_layout_hpaddings.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<android.support.percent.PercentRelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="48dip"
+    android:paddingRight="64dip">
+    <View
+        android:id="@+id/child1"
+        app:layout_widthPercent="50%"
+        android:layout_height="match_parent"
+        android:layout_alignParentLeft="true"
+        android:background="#80FF0000" />
+    <View
+        android:id="@+id/child2"
+        app:layout_widthPercent="50%"
+        android:layout_height="match_parent"
+        android:layout_toRightOf="@id/child1"
+        android:background="#800000FF" />
+</android.support.percent.PercentRelativeLayout>
diff --git a/percent/tests/res/layout/percent_relative_layout_vpaddings.xml b/percent/tests/res/layout/percent_relative_layout_vpaddings.xml
new file mode 100644
index 0000000..33defde
--- /dev/null
+++ b/percent/tests/res/layout/percent_relative_layout_vpaddings.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<android.support.percent.PercentRelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="48dip"
+    android:paddingBottom="64dip">
+    <View
+        android:id="@+id/child1"
+        android:layout_width="match_parent"
+        app:layout_heightPercent="50%"
+        android:layout_alignParentTop="true"
+        android:background="#80FF0000" />
+    <View
+        android:id="@+id/child2"
+        android:layout_width="match_parent"
+        app:layout_heightPercent="50%"
+        android:layout_below="@id/child1"
+        android:background="#800000FF" />
+</android.support.percent.PercentRelativeLayout>
diff --git a/v13/api/current.txt b/v13/api/current.txt
index 601b4d2..b965a4e 100644
--- a/v13/api/current.txt
+++ b/v13/api/current.txt
@@ -42,21 +42,17 @@
 
 package android.support.v13.view {
 
-  public abstract class DragStartHelper implements android.view.View.OnLongClickListener android.view.View.OnTouchListener {
-    ctor public DragStartHelper(android.view.View);
+  public class DragStartHelper {
+    ctor public DragStartHelper(android.view.View, android.support.v13.view.DragStartHelper.OnDragStartListener);
     method public void attach();
-    method public android.view.View.DragShadowBuilder getShadowBuilder(android.view.View);
-    method public void getTouchPosition(android.view.View, android.graphics.Point);
-    method public boolean handleLongClick(android.view.View);
-    method public boolean handleTouch(android.view.View, android.view.MotionEvent);
-    method protected abstract boolean onDragStart(android.view.View);
+    method public void detach();
+    method public void getTouchPosition(android.graphics.Point);
     method public boolean onLongClick(android.view.View);
     method public boolean onTouch(android.view.View, android.view.MotionEvent);
-    method public void remove();
   }
 
-  public class DragStartHelper.ShadowBuilder extends android.view.View.DragShadowBuilder {
-    ctor public DragStartHelper.ShadowBuilder(android.view.View);
+  public static abstract interface DragStartHelper.OnDragStartListener {
+    method public abstract boolean onDragStart(android.view.View, android.support.v13.view.DragStartHelper);
   }
 
   public final class DropPermissionsCompat {
diff --git a/v13/java/android/support/v13/view/DragStartHelper.java b/v13/java/android/support/v13/view/DragStartHelper.java
index 71c4e13..d3b51cc 100644
--- a/v13/java/android/support/v13/view/DragStartHelper.java
+++ b/v13/java/android/support/v13/view/DragStartHelper.java
@@ -30,15 +30,22 @@
  * click and drag for mouse).
  * <p>
  * It also keeps track of the screen location where the drag started, and helps determining
- * the hot spot position for drag shadow.
+ * the hot spot position for a drag shadow.
  * <p>
- * Implement {@link #onDragStart(View)} method to start the drag operation:
+ * Implement {@link DragStartHelper.OnDragStartListener} to start the drag operation:
  * <pre>
- * mDragStartHelper = new DragStartHelper(mDraggableView) {
- *     protected void onDragStart(View v) {
- *         v.startDrag(mClipData, getShadowBuilder(view), mLocalState, mDragFlags);
+ * DragStartHelper.OnDragStartListener listener = new DragStartHelper.OnDragStartListener {
+ *     protected void onDragStart(View view, DragStartHelper helper) {
+ *         View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
+ *             public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+ *                 super.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
+ *                 helper.getTouchPosition(shadowTouchPoint);
+ *             }
+ *         };
+ *         view.startDrag(mClipData, shadowBuilder, mLocalState, mDragFlags);
  *     }
  * };
+ * mDragStartHelper = new DragStartHelper(mDraggableView, listener);
  * </pre>
  * Once created, DragStartHelper can be attached to a view (this will replace existing long click
  * and touch listeners):
@@ -48,25 +55,48 @@
  * It may also be used in combination with existing listeners:
  * <pre>
  * public boolean onTouch(View view, MotionEvent event) {
- *     return mDragStartHelper.handleTouch(view, event) || handleTouchEvent(view, event);
+ *     if (mDragStartHelper.onTouch(view, event)) {
+ *         return true;
+ *     }
+ *     return handleTouchEvent(view, event);
  * }
  * public boolean onLongClick(View view) {
- *     return mDragStartHelper.handleLongClick(view) || handleLongClickEvent(view);
+ *     if (mDragStartHelper.onLongClick(view)) {
+ *         return true;
+ *     }
+ *     return handleLongClickEvent(view);
  * }
  * </pre>
  */
-public abstract class DragStartHelper implements View.OnLongClickListener, View.OnTouchListener {
+public class DragStartHelper {
     final private View mView;
-    private float mLastTouchRawX, mLastTouchRawY;
+    final private OnDragStartListener mListener;
+
+    private int mLastTouchX, mLastTouchY;
+
+    /**
+     * Interface definition for a callback to be invoked when a drag start gesture is detected.
+     */
+    public interface OnDragStartListener {
+        /**
+         * Called when a drag start gesture has been detected.
+         *
+         * @param v The view over which the drag start gesture has been detected.
+         * @param helper The DragStartHelper object which detected the gesture.
+         * @return True if the listener has consumed the event, false otherwise.
+         */
+        boolean onDragStart(View v, DragStartHelper helper);
+    }
 
     /**
      * Create a DragStartHelper associated with the specified view.
-     * The newly created helper is not initally attached to the view, {@link #attach} must be
+     * The newly created helper is not initially attached to the view, {@link #attach} must be
      * called explicitly.
      * @param view A View
      */
-    public DragStartHelper(View view) {
+    public DragStartHelper(View view, OnDragStartListener listener) {
         mView = view;
+        mListener = listener;
     }
 
     /**
@@ -75,8 +105,8 @@
      * This will replace previously existing touch and long click listeners.
      */
     public void attach() {
-        mView.setOnLongClickListener(this);
-        mView.setOnTouchListener(this);
+        mView.setOnLongClickListener(mLongClickListener);
+        mView.setOnTouchListener(mTouchListener);
     }
 
     /**
@@ -84,21 +114,11 @@
      * <p>
      * This will reset touch and long click listeners to {@code null}.
      */
-    public void remove() {
+    public void detach() {
         mView.setOnLongClickListener(null);
         mView.setOnTouchListener(null);
     }
 
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        return handleTouch(v, event);
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        return handleLongClick(v);
-    }
-
     /**
      * Handle a touch event.
      * @param v The view the touch event has been dispatched to.
@@ -106,16 +126,16 @@
      *        the event.
      * @return True if the listener has consumed the event, false otherwise.
      */
-    public boolean handleTouch(View v, MotionEvent event) {
+    public boolean onTouch(View v, MotionEvent event) {
         if (event.getAction() == MotionEvent.ACTION_DOWN ||
                 event.getAction() == MotionEvent.ACTION_MOVE) {
-            mLastTouchRawX = event.getRawX();
-            mLastTouchRawY = event.getRawY();
+            mLastTouchX = (int) event.getX();
+            mLastTouchY = (int) event.getY();
         }
         if (event.getAction() == MotionEvent.ACTION_MOVE &&
                 MotionEventCompat.isFromSource(event, InputDeviceCompat.SOURCE_MOUSE) &&
                 (MotionEventCompat.getButtonState(event) & MotionEventCompat.BUTTON_PRIMARY) != 0) {
-            return onDragStart(v);
+            return mListener.onDragStart(v, this);
         }
         return false;
     }
@@ -125,62 +145,30 @@
      * @param v The view that was clicked and held.
      * @return true if the callback consumed the long click, false otherwise.
      */
-    public boolean handleLongClick(View v) {
-        return onDragStart(v);
+    public boolean onLongClick(View v) {
+        return mListener.onDragStart(v, this);
     }
 
     /**
      * Compute the position of the touch event that started the drag operation.
-     * @param v The view relative to which the position should be computed.
      * @param point The position of the touch event that started the drag operation.
      */
-    public void getTouchPosition(View v, Point point) {
-        int [] viewLocation = new int[2];
-        v.getLocationOnScreen(viewLocation);
-        point.set((int) mLastTouchRawX - viewLocation[0], (int) mLastTouchRawY - viewLocation[1]);
+    public void getTouchPosition(Point point) {
+        point.set(mLastTouchX, mLastTouchY);
     }
 
-    /**
-     * Create a {@link View.DragShadowBuilder} which will build a drag shadow with the same
-     * appearance and dimensions as the specified view and with the hot spot at the location of
-     * the touch event that started the drag operation.
-     * @param view A view
-     * @return  {@link View.DragShadowBuilder}
-     */
-    public View.DragShadowBuilder getShadowBuilder(View view) {
-        return new ShadowBuilder(view);
-    }
-
-    /**
-     * A utility class that builds a drag shadow with the same appearance and dimensions as the
-     * specified view and with the hot spot at the location of the touch event that started the
-     * drag operation.
-     * <p>
-     * At the start of the drag operation a drag shadow built this way will be positioned directly
-     * over the specified view.
-     */
-    public class ShadowBuilder extends View.DragShadowBuilder {
-        /**
-         * Constructs a shadow image builder based on a View. By default, the resulting drag
-         * shadow will have the same appearance and dimensions as the View, with the touch point
-         * at the location of the touch event that started the drag operation.
-         * @param view A view.
-         */
-        public ShadowBuilder(View view) {
-            super(view);
-        }
-
+    private final View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
         @Override
-        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
-            super.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
-            getTouchPosition(getView(), shadowTouchPoint);
+        public boolean onLongClick(View v) {
+            return DragStartHelper.this.onLongClick(v);
         }
-    }
+    };
 
-    /**
-     * Called when the drag start gesture has been detected.
-     * @param v A view on which the drag start gesture has been detected.
-     */
-    protected abstract boolean onDragStart(View v);
+    private final View.OnTouchListener mTouchListener = new View.OnTouchListener() {
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            return DragStartHelper.this.onTouch(v, event);
+        }
+    };
 }
 
diff --git a/v14/preference/Android.mk b/v14/preference/Android.mk
index f01c53f..a9ff72c 100644
--- a/v14/preference/Android.mk
+++ b/v14/preference/Android.mk
@@ -14,41 +14,34 @@
 
 LOCAL_PATH := $(call my-dir)
 
-# Build the resources using the latest applicable SDK version.
-# We do this here because the final static library must be compiled with an older
-# SDK version than the resources.  The resources library and the R class that it
-# contains will not be linked into the final static library.
-include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-v14-preference-res
-LOCAL_SRC_FILES := $(call all-java-files-under, ../../v7/preference/constants)
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_RESOURCE_DIR := \
-        frameworks/support/v7/appcompat/res \
-        frameworks/support/v7/preference/res \
-        $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
-	--auto-add-overlay
-LOCAL_JAR_EXCLUDE_FILES := none
-LOCAL_JAVA_LANGUAGE_VERSION := 1.7
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v14-preference \
+#       android-support-v7-preference \
+#       android-support-v7-appcompat \
+#       android-support-v7-recyclerview \
+#       android-support-v4 \
+#       android-support-annotations
+#
 # in their makefiles to include the resources in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v14-preference
 LOCAL_SDK_VERSION := 14
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
-# LOCAL_STATIC_JAVA_LIBRARIES :=
-LOCAL_JAVA_LIBRARIES := \
-        android-support-v4 \
-        android-support-v7-appcompat \
-        android-support-v7-recyclerview \
-        android-support-v7-preference \
-        android-support-annotations \
-        android-support-v14-preference-res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v7-preference \
+    android-support-v7-appcompat \
+    android-support-v7-recyclerview \
+    android-support-v4 \
+    android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
index 8316b0d..1695e92 100644
--- a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
+++ b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
@@ -53,16 +53,16 @@
         super(context, attrs, defStyleAttr, defStyleRes);
 
         final TypedArray a = context.obtainStyledAttributes(attrs,
-                R.styleable.MultiSelectListPreference, defStyleAttr,
+                android.support.v7.preference.R.styleable.MultiSelectListPreference, defStyleAttr,
                 defStyleRes);
 
         mEntries = TypedArrayUtils.getTextArray(a,
-                R.styleable.MultiSelectListPreference_entries,
-                R.styleable.MultiSelectListPreference_android_entries);
+                android.support.v7.preference.R.styleable.MultiSelectListPreference_entries,
+                android.support.v7.preference.R.styleable.MultiSelectListPreference_android_entries);
 
         mEntryValues = TypedArrayUtils.getTextArray(a,
-                R.styleable.MultiSelectListPreference_entryValues,
-                R.styleable.MultiSelectListPreference_android_entryValues);
+                android.support.v7.preference.R.styleable.MultiSelectListPreference_entryValues,
+                android.support.v7.preference.R.styleable.MultiSelectListPreference_android_entryValues);
 
         a.recycle();
     }
@@ -72,7 +72,8 @@
     }
 
     public MultiSelectListPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+        this(context, attrs, TypedArrayUtils.getAttr(context,
+                android.support.v7.preference.R.attr.dialogPreferenceStyle,
                 android.R.attr.dialogPreferenceStyle));
     }
 
diff --git a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
index 43e487c..f2d9549 100644
--- a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
+++ b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
@@ -136,7 +136,7 @@
 
     private Context mStyledContext;
 
-    private int mLayoutResId = R.layout.preference_list_fragment;
+    private int mLayoutResId = android.support.v7.preference.R.layout.preference_list_fragment;
 
     private final DividerDecoration mDividerDecoration = new DividerDecoration();
 
@@ -210,7 +210,8 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         final TypedValue tv = new TypedValue();
-        getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true);
+        getActivity().getTheme().resolveAttribute(
+                android.support.v7.preference.R.attr.preferenceTheme, tv, true);
         final int theme = tv.resourceId;
         if (theme <= 0) {
             throw new IllegalStateException("Must specify preferenceTheme in theme");
@@ -247,7 +248,8 @@
 
         TypedArray a = mStyledContext.obtainStyledAttributes(null,
                 R.styleable.PreferenceFragment,
-                TypedArrayUtils.getAttr(mStyledContext, R.attr.preferenceFragmentStyle,
+                TypedArrayUtils.getAttr(mStyledContext,
+                        android.support.v7.preference.R.attr.preferenceFragmentStyle,
                         AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE),
                 0);
 
@@ -261,7 +263,8 @@
 
         // Need to theme the inflater to pick up the preferenceFragmentListStyle
         final TypedValue tv = new TypedValue();
-        getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true);
+        getActivity().getTheme().resolveAttribute(
+                android.support.v7.preference.R.attr.preferenceTheme, tv, true);
         final int theme = tv.resourceId;
 
         final Context themedContext = new ContextThemeWrapper(inflater.getContext(), theme);
@@ -575,7 +578,8 @@
     public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
             Bundle savedInstanceState) {
         RecyclerView recyclerView = (RecyclerView) inflater
-                .inflate(R.layout.preference_recyclerview, parent, false);
+                .inflate(android.support.v7.preference.R.layout.preference_recyclerview,
+                        parent, false);
 
         recyclerView.setLayoutManager(onCreateLayoutManager());
 
diff --git a/v14/preference/src/android/support/v14/preference/SwitchPreference.java b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
index ac2a9c6..6e6b7f8 100644
--- a/v14/preference/src/android/support/v14/preference/SwitchPreference.java
+++ b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
@@ -122,7 +122,8 @@
      * @param attrs Style attributes that differ from the default
      */
     public SwitchPreference(Context context, AttributeSet attrs) {
-        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.switchPreferenceStyle,
+        this(context, attrs, TypedArrayUtils.getAttr(context,
+                android.support.v7.preference.R.attr.switchPreferenceStyle,
                 android.R.attr.switchPreferenceStyle));
     }
 
@@ -216,7 +217,7 @@
             return;
         }
 
-        View switchView = view.findViewById(R.id.switchWidget);
+        View switchView = view.findViewById(android.support.v7.preference.R.id.switchWidget);
         syncSwitchView(switchView);
 
         View summaryView = view.findViewById(android.R.id.summary);
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index d55ae61..8e6a224 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -19,12 +19,11 @@
 # SDK version than the resources.  The resources library and the R class that it
 # contains will not be linked into the final static library.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v17-leanback-res
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
-        --auto-add-overlay
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -99,21 +98,34 @@
 # -----------------------------------------------------------------------
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v17-leanback \
+#       android-support-v7-recyclerview \
+#       android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v17-leanback
 LOCAL_SDK_VERSION := 17
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v17-leanback-kitkat android-support-v17-leanback-jbmr2 \
-        android-support-v17-leanback-api23 \
-        android-support-v17-leanback-api21 android-support-v17-leanback-common
-LOCAL_JAVA_LIBRARIES := \
-        android-support-v4 \
-        android-support-v7-recyclerview \
-        android-support-v17-leanback-res
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v17-leanback-kitkat \
+    android-support-v17-leanback-jbmr2 \
+    android-support-v17-leanback-api23 \
+    android-support-v17-leanback-api21 \
+    android-support-v17-leanback-common
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    android-support-v17-leanback-res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v7-recyclerview \
+    android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index f9c13de..dc5da07 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -101,6 +101,11 @@
     method public abstract T createFragment(java.lang.Object);
   }
 
+  public static abstract interface BrowseFragment.FragmentHost {
+    method public abstract void notifyViewCreated(android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter);
+    method public abstract void showTitleView(boolean);
+  }
+
   public static class BrowseFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseFragment.FragmentFactory {
     ctor public BrowseFragment.ListRowFragmentFactory();
     method public android.support.v17.leanback.app.RowsFragment createFragment(java.lang.Object);
@@ -109,6 +114,7 @@
   public static class BrowseFragment.MainFragmentAdapter {
     ctor public BrowseFragment.MainFragmentAdapter(T);
     method public final T getFragment();
+    method public final android.support.v17.leanback.app.BrowseFragment.FragmentHost getFragmentHost();
     method public boolean isScalingEnabled();
     method public boolean isScrolling();
     method public void onTransitionEnd();
@@ -190,6 +196,11 @@
     method public abstract T createFragment(java.lang.Object);
   }
 
+  public static abstract interface BrowseSupportFragment.FragmentHost {
+    method public abstract void notifyViewCreated(android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter);
+    method public abstract void showTitleView(boolean);
+  }
+
   public static class BrowseSupportFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory {
     ctor public BrowseSupportFragment.ListRowFragmentFactory();
     method public android.support.v17.leanback.app.RowsSupportFragment createFragment(java.lang.Object);
@@ -198,6 +209,7 @@
   public static class BrowseSupportFragment.MainFragmentAdapter {
     ctor public BrowseSupportFragment.MainFragmentAdapter(T);
     method public final T getFragment();
+    method public final android.support.v17.leanback.app.BrowseSupportFragment.FragmentHost getFragmentHost();
     method public boolean isScalingEnabled();
     method public boolean isScrolling();
     method public void onTransitionEnd();
@@ -898,6 +910,34 @@
     method public android.widget.TextView getTitle();
   }
 
+  public abstract class AbstractMediaItemPresenter extends android.support.v17.leanback.widget.RowPresenter {
+    ctor public AbstractMediaItemPresenter(android.content.Context, int);
+    ctor public AbstractMediaItemPresenter();
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method protected abstract void onBindMediaViewHolder(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder, java.lang.Object);
+    method public void setBackgroundColor(int);
+  }
+
+  public static class AbstractMediaItemPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+    ctor public AbstractMediaItemPresenter.ViewHolder(android.view.View);
+    method public android.widget.TextView getTrackDurationView();
+    method public android.widget.TextView getTrackNameView();
+    method public android.widget.TextView getTrackNumberView();
+  }
+
+  public abstract class AbstractMediaListHeaderPresenter extends android.support.v17.leanback.widget.RowPresenter {
+    ctor public AbstractMediaListHeaderPresenter(android.content.Context, int);
+    ctor public AbstractMediaListHeaderPresenter();
+    method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+    method protected abstract void onBindMediaListHeaderViewHolder(android.support.v17.leanback.widget.AbstractMediaListHeaderPresenter.ViewHolder, java.lang.Object);
+    method public void setBackgroundColor(int);
+  }
+
+  public static class AbstractMediaListHeaderPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+    ctor public AbstractMediaListHeaderPresenter.ViewHolder(android.view.View);
+    method public android.widget.TextView getHeaderView();
+  }
+
   public class Action {
     ctor public Action(long);
     ctor public Action(long, java.lang.CharSequence);
@@ -914,6 +954,7 @@
     method public final void setId(long);
     method public final void setLabel1(java.lang.CharSequence);
     method public final void setLabel2(java.lang.CharSequence);
+    field public static final long NO_ID = -1L; // 0xffffffffffffffffL
   }
 
   public class ArrayObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
@@ -1219,12 +1260,15 @@
     method public boolean hasSubActions();
     method public boolean hasTextEditable();
     method public boolean infoOnly();
+    method public final boolean isAutoSaveRestoreEnabled();
     method public boolean isChecked();
     method public boolean isDescriptionEditable();
     method public boolean isEditTitleUsed();
     method public boolean isEditable();
     method public boolean isEnabled();
     method public boolean isFocusable();
+    method public void onRestoreInstanceState(android.os.Bundle, java.lang.String);
+    method public void onSaveInstanceState(android.os.Bundle, java.lang.String);
     method public void setChecked(boolean);
     method public void setDescription(java.lang.CharSequence);
     method public void setEditDescription(java.lang.CharSequence);
@@ -1256,6 +1300,7 @@
   public static abstract class GuidedAction.BuilderBase {
     ctor public GuidedAction.BuilderBase(android.content.Context);
     method protected final void applyValues(android.support.v17.leanback.widget.GuidedAction);
+    method public B autoSaveRestoreEnabled(boolean);
     method public B checkSetId(int);
     method public B checked(boolean);
     method public B clickAction(long);
diff --git a/v17/leanback/res/animator/lb_page_indicator_dot_hide.xml b/v17/leanback/res/animator/lb_page_indicator_dot_hide.xml
deleted file mode 100644
index fdd6034..0000000
--- a/v17/leanback/res/animator/lb_page_indicator_dot_hide.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 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.
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <objectAnimator
-        android:propertyName="alpha"
-        android:valueFrom="1.0"
-        android:valueTo="0.0"
-        android:duration="167"
-        android:interpolator="@android:anim/decelerate_interpolator" />
-    <objectAnimator
-        android:propertyName="diameter"
-        android:valueFrom="36dp"
-        android:valueTo="10dp"
-        android:duration="417"
-        android:interpolator="@android:anim/decelerate_interpolator" />
-    <objectAnimator
-        android:propertyName="translationX"
-        android:valueFrom="-16dp"
-        android:valueTo="0dp"
-        android:duration="417"
-        android:interpolator="@android:anim/decelerate_interpolator" />
-</set>
diff --git a/v17/leanback/res/animator/lb_page_indicator_dot_show.xml b/v17/leanback/res/animator/lb_page_indicator_dot_show.xml
deleted file mode 100644
index 392fb2c..0000000
--- a/v17/leanback/res/animator/lb_page_indicator_dot_show.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 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.
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <objectAnimator
-        android:propertyName="alpha"
-        android:valueFrom="0.0"
-        android:valueTo="1.0"
-        android:duration="167"
-        android:interpolator="@android:anim/decelerate_interpolator" />
-    <objectAnimator
-        android:propertyName="diameter"
-        android:valueFrom="10dp"
-        android:valueTo="36dp"
-        android:duration="417"
-        android:interpolator="@android:anim/decelerate_interpolator" />
-    <objectAnimator
-        android:propertyName="translationX"
-        android:valueFrom="-16dp"
-        android:valueTo="0dp"
-        android:duration="417"
-        android:interpolator="@android:anim/decelerate_interpolator" />
-</set>
diff --git a/v17/leanback/res/layout/lb_guidedstep_fragment.xml b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
index 2ffae70..adf9d4f 100644
--- a/v17/leanback/res/layout/lb_guidedstep_fragment.xml
+++ b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
@@ -68,6 +68,8 @@
 
                 <android.support.v17.leanback.widget.NonOverlappingLinearLayout
                     android:id="@+id/action_fragment"
+                    android:focusable="true"
+                    android:descendantFocusability="afterDescendants"
                     android:transitionName="action_fragment"
                     android:transitionGroup="false"
                     android:orientation="horizontal"
diff --git a/v17/leanback/res/layout/lb_onboarding_fragment.xml b/v17/leanback/res/layout/lb_onboarding_fragment.xml
index 0484b87..04bd0ea 100644
--- a/v17/leanback/res/layout/lb_onboarding_fragment.xml
+++ b/v17/leanback/res/layout/lb_onboarding_fragment.xml
@@ -16,7 +16,6 @@
   -->
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:lb="http://schemas.android.com/apk/res-auto"
     android:id="@+id/onboarding_fragment_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -53,11 +52,9 @@
     <FrameLayout
         android:id="@id/navigator_container"
         style="?attr/onboardingNavigatorContainerStyle">
-        <!-- TODO: Customize dimensions of PagingIndicator -->
         <android.support.v17.leanback.widget.PagingIndicator
             android:id="@+id/page_indicator"
             style="?attr/onboardingPageIndicatorStyle"
-            lb:dotBgColor="?attr/onboardingNavigatorDotBgColor"
             android:visibility="gone" />
 
         <Button
diff --git a/v17/leanback/res/layout/lb_playback_controls_row.xml b/v17/leanback/res/layout/lb_playback_controls_row.xml
index a58840d..e3893b3 100644
--- a/v17/leanback/res/layout/lb_playback_controls_row.xml
+++ b/v17/leanback/res/layout/lb_playback_controls_row.xml
@@ -18,14 +18,15 @@
 <!-- Note: clipChildren/clipToPadding false are needed to apply shadows to child
      views with no padding of their own. Also to allow for negative margin on description. -->
 
-<android.support.v17.leanback.widget.PlaybackControlsRowView xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.v17.leanback.widget.PlaybackControlsRowView
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:paddingStart="@dimen/lb_playback_controls_margin_start"
-    android:paddingEnd="@dimen/lb_playback_controls_margin_end" >
+    android:paddingStart="?attr/playbackPaddingStart"
+    android:paddingEnd="?attr/playbackPaddingEnd" >
 
     <LinearLayout
         android:id="@+id/controls_card"
@@ -88,4 +89,4 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/lb_playback_controls_margin_bottom" />
 
-</android.support.v17.leanback.widget.PlaybackControlsRowView>
\ No newline at end of file
+</android.support.v17.leanback.widget.PlaybackControlsRowView>
diff --git a/v17/leanback/res/layout/lb_row_media_item.xml b/v17/leanback/res/layout/lb_row_media_item.xml
new file mode 100644
index 0000000..7f01068
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_media_item.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:lb="http://schemas.android.com/apk/res-auto"
+             android:id="@+id/rowContainer"
+             style="?attr/playbackMediaItemStyle">
+
+
+    <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+        <TextView
+                android:id="@+id/trackNumber"
+                style="?attr/playbackMediaItemTrackNumberStyle"/>
+
+
+        <TextView
+                android:id="@+id/trackName"
+                android:layout_toEndOf="@+id/trackNumber"
+                android:layout_toLeftOf="@+id/trackDuration"
+                style="?attr/playbackMediaItemTrackNameStyle"/>
+
+
+        <TextView
+                android:id="@+id/trackDuration"
+                android:gravity="center_vertical|right"
+                style="?attr/playbackMediaItemTrackDurationStyle"/>
+    </RelativeLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_row_track_list_header.xml b/v17/leanback/res/layout/lb_row_track_list_header.xml
new file mode 100644
index 0000000..57cedb5
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_track_list_header.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             xmlns:lb="http://schemas.android.com/apk/res-auto"
+             style="?attr/playbackMediaListHeaderStyle">
+
+        <TextView
+                android:id="@+id/trackListHeader"
+                style="?attr/playbackMediaListHeaderTitleStyle"
+        />
+</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_title_out.xml b/v17/leanback/res/transition-v21/lb_title_out.xml
index 50fb67e..69735ab 100644
--- a/v17/leanback/res/transition-v21/lb_title_out.xml
+++ b/v17/leanback/res/transition-v21/lb_title_out.xml
@@ -18,7 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:lb="http://schemas.android.com/apk/res-auto"
     class="android.support.v17.leanback.transition.SlideKitkat"
-    android:interpolator="@animator/lb_decelerator_4"
+    android:interpolator="@animator/lb_decelerator_2"
     lb:lb_slideEdge="top" >
     <targets>
         <target android:targetId="@id/browse_title_group" />
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index 7ac192f..28e3807 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -267,7 +267,18 @@
         <attr name="detailsDescriptionBodyStyle" format="reference" />
         <attr name="detailsActionButtonStyle" format="reference" />
 
-        <!-- for playback controls -->
+        <!-- for playlist and playback controls styling -->
+        <attr name="playbackPaddingStart" format="dimension"/>
+        <attr name="playbackPaddingEnd" format="dimension"/>
+
+        <attr name="playbackMediaListHeaderStyle" format="reference"/>
+        <attr name="playbackMediaItemStyle" format="reference"/>
+
+        <attr name="playbackMediaListHeaderTitleStyle" format="reference"/>
+        <attr name="playbackMediaItemTrackNumberStyle" format="reference"/>
+        <attr name="playbackMediaItemTrackNameStyle" format="reference"/>
+        <attr name="playbackMediaItemTrackDurationStyle" format="reference"/>
+
         <attr name="playbackControlsButtonStyle" format="reference" />
         <attr name="playbackControlButtonLabelStyle" format="reference" />
         <attr name="playbackControlsTimeStyle" format="reference" />
@@ -520,15 +531,20 @@
         <!-- Theme attribute for the style of the logo in onboarding screen. Default is
              {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingLogoStyle}.-->
         <attr name="onboardingLogoStyle" format="reference" />
-
-        <!-- Theme attribute for the style of the background color of the dots in the navigator
-             of the onboarding screen. -->
-        <attr name="onboardingNavigatorDotBgColor" format="reference" />
-
     </declare-styleable>
 
     <declare-styleable name="PagingIndicator">
+        <!-- Attributes for the radius of the dot. -->
+        <attr name="dotRadius" format="reference" />
+        <!-- Attributes for the radius of the arrow. -->
+        <attr name="arrowRadius" format="reference" />
+        <!-- Attributes for the distance between the centers of the adjacent dots. -->
+        <attr name="dotToDotGap" format="reference" />
+        <!-- Attributes for the distance between the centers of the arrow circle and the adjacent dot. -->
+        <attr name="dotToArrowGap" format="reference" />
         <!-- Attribute for background color of the dots in PagingIndicator. -->
         <attr name="dotBgColor" format="reference" />
+        <!-- Attribute for background color of the arrow in PagingIndicator. -->
+        <attr name="arrowBgColor" format="reference" />
     </declare-styleable>
 </resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index 063de2e..8b2fc22 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -277,14 +277,12 @@
     <dimen name="lb_onboarding_content_margin_top">164dp</dimen>
     <!-- This value should be lb_onboarding_start_button_height + lb_onboarding_start_button_margin_bottom -->
     <dimen name="lb_onboarding_content_margin_bottom">98dp</dimen>
-    <!-- This value should be lb_page_indicator_arrow_diameter + 2 * lb_page_indicator_arrow_shadow_radius -->
+    <!-- This value should be 2 * lb_page_indicator_arrow_radius + 2 * lb_page_indicator_arrow_shadow_radius -->
     <dimen name="lb_onboarding_navigation_height">40dp</dimen>
-    <dimen name="lb_page_indicator_arrow_diameter">36dp</dimen>
+    <dimen name="lb_page_indicator_arrow_radius">18dp</dimen>
     <dimen name="lb_page_indicator_arrow_shadow_radius">2dp</dimen>
     <dimen name="lb_page_indicator_arrow_shadow_offset">1dp</dimen>
     <dimen name="lb_page_indicator_arrow_gap">32dp</dimen>
-    <!-- This value must be twice lb_page_indicator_dot_radius -->
-    <dimen name="lb_page_indicator_dot_diameter">10dp</dimen>
     <dimen name="lb_page_indicator_dot_radius">5dp</dimen>
     <dimen name="lb_page_indicator_dot_gap">16dp</dimen>
     <dimen name="lb_onboarding_start_button_translation_offset">16dp</dimen>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index 9dfdee2..8156b82 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -285,6 +285,95 @@
         <item name="android:maxLines">4</item>
     </style>
 
+
+    <!-- Styles for playback control, playlist header, and playlist content in a default media player layout. -->
+    <style name="Widget.Leanback.PlaybackRow">
+        <item name="android:layout_marginStart">?attr/playbackPaddingStart</item>
+        <item name="android:layout_marginEnd">?attr/playbackPaddingEnd</item>
+        <item name="android:clipChildren">false</item>
+        <item name="android:clipToPadding">false</item>
+        <item name="android:foreground">@null</item>
+        <item name="android:background">?attr/defaultBrandColor</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">48dp</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackMediaItemStyle" parent="Widget.Leanback.PlaybackRow">
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+        <item name="android:foreground">?android:attr/selectableItemBackground</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackMediaListHeaderStyle"
+           parent="Widget.Leanback.PlaybackRow">
+        <item name="android:focusable">false</item>
+        <item name="android:focusableInTouchMode">false</item>
+    </style>
+
+
+    <style name="TextAppearance.Leanback.PlaybackMediaListHeaderTitle">
+        <item name="android:textColor">#80EEEEEE</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackMediaListHeaderTitleStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_alignParentStart">true</item>
+        <item name="android:layout_alignParentTop">true</item>
+        <item name="android:gravity">center_vertical</item>"
+        <item name="android:paddingLeft">32dp</item>
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaListHeaderTitle</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.PlaybackMediaItemTrackNumber">
+        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackMediaItemTrackNumberStyle">
+        <item name="android:layout_width">56dp</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_marginLeft">32dp</item>
+        <item name="android:gravity">center_vertical</item>"
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemTrackNumber</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.PlaybackMediaItemTrackName">
+        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackMediaItemTrackNameStyle">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_alignParentTop">true</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:gravity">center_vertical</item>"
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemTrackName</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.PlaybackMediaItemTrackDuration">
+        <item name="android:textColor">#80FFFFFF</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackMediaItemTrackDurationStyle">
+        <item name="android:layout_width">66dp</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_alignParentEnd">true</item>
+        <item name="android:layout_alignParentTop">true</item>
+        <item name="android:layout_marginRight">32dp</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:gravity">center_vertical|right</item>
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemTrackDuration</item>
+    </style>
+
+
     <style name="Widget.Leanback.DetailsDescriptionTitleStyle">
         <item name="android:textAlignment">viewStart</item>
         <item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionTitle</item>
@@ -460,8 +549,8 @@
 
     <!-- Style for the vertical grid of sub actions in a GuidedActionsStylist's default layout. -->
     <style name="Widget.Leanback.GuidedSubActionsListStyle" parent="Widget.Leanback.GuidedActionsListStyle">
-        <item name="focusOutEnd">false</item>
-        <item name="focusOutFront">false</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
         <item name="focusOutSideStart">false</item>
         <item name="focusOutSideEnd">false</item>
         <item name="android:layout_marginBottom">@dimen/lb_guidedactions_sublist_bottom_margin</item>
@@ -611,6 +700,12 @@
         <item name="android:layout_gravity">center_horizontal</item>
         <item name="android:focusable">true</item>
         <item name="android:contentDescription">@string/lb_onboarding_accessibility_next</item>
+        <item name="dotRadius">@dimen/lb_page_indicator_dot_radius</item>
+        <item name="arrowRadius">@dimen/lb_page_indicator_arrow_radius</item>
+        <item name="dotToDotGap">@dimen/lb_page_indicator_dot_gap</item>
+        <item name="dotToArrowGap">@dimen/lb_page_indicator_arrow_gap</item>
+        <item name="dotBgColor">@color/lb_page_indicator_dot</item>
+        <item name="arrowBgColor">@color/lb_page_indicator_arrow_background</item>
     </style>
 
     <!-- Style for the start button in OnboardingFragment. -->
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index df5bb67..2af382f 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -64,15 +64,28 @@
 
         <item name="searchOrbViewStyle">@style/Widget.Leanback.SearchOrbViewStyle</item>
 
+
         <item name="detailsDescriptionTitleStyle">@style/Widget.Leanback.DetailsDescriptionTitleStyle</item>
         <item name="detailsDescriptionSubtitleStyle">@style/Widget.Leanback.DetailsDescriptionSubtitleStyle</item>
         <item name="detailsDescriptionBodyStyle">@style/Widget.Leanback.DetailsDescriptionBodyStyle</item>
         <item name="detailsActionButtonStyle">@style/Widget.Leanback.DetailsActionButtonStyle</item>
+        <!-- Attributes used for styling of a playback -->
+        <item name="playbackPaddingStart">@dimen/lb_playback_controls_margin_start</item>
+        <item name="playbackPaddingEnd">@dimen/lb_playback_controls_margin_end</item>
+
+        <item name="playbackMediaListHeaderStyle">@style/Widget.Leanback.PlaybackMediaListHeaderStyle</item>
+        <item name="playbackMediaItemStyle">@style/Widget.Leanback.PlaybackMediaItemStyle</item>
+        <item name="playbackMediaListHeaderTitleStyle">@style/Widget.Leanback.PlaybackMediaListHeaderTitleStyle</item>
+        <item name="playbackMediaItemTrackNumberStyle">@style/Widget.Leanback.PlaybackMediaItemTrackNumberStyle</item>
+        <item name="playbackMediaItemTrackNameStyle">@style/Widget.Leanback.PlaybackMediaItemTrackNameStyle</item>
+        <item name="playbackMediaItemTrackDurationStyle">@style/Widget.Leanback.PlaybackMediaItemTrackDurationStyle</item>
+
         <item name="playbackControlsButtonStyle">@style/Widget.Leanback.PlaybackControlsButtonStyle</item>
         <item name="playbackControlButtonLabelStyle">@style/Widget.Leanback.PlaybackControlLabelStyle</item>
         <item name="playbackControlsTimeStyle">@style/Widget.Leanback.PlaybackControlsTimeStyle</item>
         <item name="playbackControlsActionIcons">@style/Widget.Leanback.PlaybackControlsActionIconsStyle</item>
 
+
         <item name="errorMessageStyle">@style/Widget.Leanback.ErrorMessageStyle</item>
 
         <!-- TODO should be null, and set programatically to avoid dependence on leanback theme -->
@@ -185,6 +198,5 @@
         <item name="onboardingPageIndicatorStyle">@style/Widget.Leanback.OnboardingPageIndicatorStyle</item>
         <item name="onboardingStartButtonStyle">@style/Widget.Leanback.OnboardingStartButtonStyle</item>
         <item name="onboardingLogoStyle">@style/Widget.Leanback.OnboardingLogoStyle</item>
-        <item name="onboardingNavigatorDotBgColor">@color/lb_page_indicator_dot</item>
     </style>
 </resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
index f454144..a11b623 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
@@ -239,4 +239,13 @@
             mTitleView.enableAnimation(true);
         }
     }
+
+    /**
+     * Returns true/false when it's showing/hiding the title respectively.
+     *
+     * @return boolean to indicate whether or not it's showing the title.
+     */
+    public final boolean isShowingTitle() {
+        return mShowingTitle;
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
index 18fae4f..b6c7b63 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
@@ -241,4 +241,13 @@
             mTitleView.enableAnimation(true);
         }
     }
+
+    /**
+     * Returns true/false when it's showing/hiding the title respectively.
+     *
+     * @return boolean to indicate whether or not it's showing the title.
+     */
+    public final boolean isShowingTitle() {
+        return mShowingTitle;
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index ca606f3..8338e5e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -200,6 +200,53 @@
     }
 
     /**
+     * Possible set of actions that {@link BrowseFragment} exposes to clients. Custom
+     * fragments can interact with {@link BrowseFragment} using this interface.
+     */
+    public interface FragmentHost {
+        /**
+         * Clients are required to invoke this callback once their view is created
+         * inside {@link Fragment#onStart} method. {@link BrowseFragment} starts the entrance
+         * animation only after receiving this callback. Failure to invoke this method
+         * will lead to fragment not showing up.
+         *
+         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+         */
+        void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
+
+        /**
+         * Slides in the title view from top in {@link BrowseFragment}. This will only happen
+         * if either a. we are in fully expanded mode OR b. non expanded mode but on the first
+         * row.
+         *
+         * @param show Boolean indicating whether or not to show the title view.
+         */
+        void showTitleView(boolean show);
+    }
+
+    /**
+     * Default implementation of {@link FragmentHost} that is used only by
+     * {@link BrowseFragment}.
+     */
+    private final class FragmentHostImpl implements FragmentHost {
+
+        @Override
+        public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
+            processingPendingEntranceTransition();
+        }
+
+        @Override
+        public void showTitleView(boolean show) {
+            if (mShowingHeaders && !isShowingTitle()) {
+                mPendingShowTitleView = true;
+            }
+            else if (!mShowingHeaders && !isShowingTitle()) {
+                showTitle(true);
+            }
+        }
+    }
+
+    /**
      * Interface that defines the interaction between {@link BrowseFragment} and it's main
      * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
      * it will be used to get the fragment to be shown in the content section. Clients can
@@ -224,6 +271,7 @@
     public static class MainFragmentAdapter<T extends Fragment> {
         private boolean mScalingEnabled;
         private final T mFragment;
+        private FragmentHost mFragmentHost;
 
         public MainFragmentAdapter(T fragment) {
             this.mFragment = fragment;
@@ -291,6 +339,14 @@
         public void setScalingEnabled(boolean scalingEnabled) {
             this.mScalingEnabled = scalingEnabled;
         }
+
+        void setFragmentHost(FragmentHost fragmentHost) {
+            this.mFragmentHost = fragmentHost;
+        }
+
+        public final FragmentHost getFragmentHost() {
+            return mFragmentHost;
+        }
     }
 
     /**
@@ -385,6 +441,7 @@
             mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
             mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
                     .getAdapter(MainFragmentAdapter.class);
+            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
             if (!mIsPageRow) {
                 mMainFragmentRowsAdapter = (MainFragmentRowsAdapter) ((Adaptable)mMainFragment)
                         .getAdapter(MainFragmentRowsAdapter.class);
@@ -481,6 +538,7 @@
     private boolean mHeadersBackStackEnabled = true;
     private String mWithHeadersBackStackName;
     private boolean mShowingHeaders = true;
+    private boolean mPendingShowTitleView;
     private boolean mCanShowHeaders = true;
     private int mContainerListMarginStart;
     private int mContainerListAlignTop;
@@ -628,7 +686,7 @@
      * Sets an item clicked listener on the fragment.
      * OnItemViewClickedListener will override {@link View.OnClickListener} that
      * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
-     * So in general,  developer should choose one of the listeners but not both.
+     * So in general, developer should choose one of the listeners but not both.
      */
     public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
         mOnItemViewClickedListener = listener;
@@ -908,6 +966,7 @@
                 // This way we can maintain the invariant that mMainFragmentAdapter is never
                 // null and it avoids doing null checks all over the code.
                 mMainFragmentAdapter = new MainFragmentAdapter(null);
+                mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
             }
 
             ft.commit();
@@ -917,6 +976,7 @@
             mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
             mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
                     .getAdapter(MainFragmentAdapter.class);
+            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
 
             mIsPageRow = savedInstanceState != null ?
                     savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
@@ -991,7 +1051,7 @@
 
     @Override
     boolean isReadyForPrepareEntranceTransition() {
-        return mMainFragment != null;
+        return mMainFragment != null && mMainFragment.getView() != null;
     }
 
     @Override
@@ -1000,14 +1060,7 @@
     }
 
     void processingPendingEntranceTransition() {
-        // mMainFragment is not null at this point, it can perform prepare entrance transition.
         performPendingStates();
-        // mMainFragment's view is going to be created in next cycle
-        getView().post(new Runnable() {
-            public void run() {
-                performPendingStates();
-            }
-        });
     }
 
     private void createHeadersTransition() {
@@ -1039,6 +1092,15 @@
                             headerGridView.requestFocus();
                         }
                     }
+
+                    // Animate titleview once header animation is complete.
+                    if (!mShowingHeaders && mPendingShowTitleView) {
+                        mPendingShowTitleView = false;
+                        showTitle(true);
+                    } else if (mShowingHeaders && isShowingTitle() && mSelectedPosition != 0) {
+                        mPendingShowTitleView = true;
+                        showTitle(false);
+                    }
                 }
                 if (mBrowseTransitionListener != null) {
                     mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
@@ -1150,6 +1212,10 @@
             return;
         }
 
+        if (position != mSelectedPosition) {
+            mPendingShowTitleView = false;
+        }
+
         mHeadersFragment.setSelectedPosition(position, smooth);
         replaceMainFragment(position);
 
@@ -1240,7 +1306,7 @@
         if (isEntranceTransitionEnabled()) {
             setEntranceTransitionStartState();
         }
-        processingPendingEntranceTransition();
+        showTitle(false);
     }
 
     private void onExpandTransitionStart(boolean expand, final Runnable callback) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
index 124d228..fc943db 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -202,6 +202,53 @@
     }
 
     /**
+     * Possible set of actions that {@link BrowseSupportFragment} exposes to clients. Custom
+     * fragments can interact with {@link BrowseSupportFragment} using this interface.
+     */
+    public interface FragmentHost {
+        /**
+         * Clients are required to invoke this callback once their view is created
+         * inside {@link Fragment#onStart} method. {@link BrowseSupportFragment} starts the entrance
+         * animation only after receiving this callback. Failure to invoke this method
+         * will lead to fragment not showing up.
+         *
+         * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+         */
+        void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
+
+        /**
+         * Slides in the title view from top in {@link BrowseSupportFragment}. This will only happen
+         * if either a. we are in fully expanded mode OR b. non expanded mode but on the first
+         * row.
+         *
+         * @param show Boolean indicating whether or not to show the title view.
+         */
+        void showTitleView(boolean show);
+    }
+
+    /**
+     * Default implementation of {@link FragmentHost} that is used only by
+     * {@link BrowseSupportFragment}.
+     */
+    private final class FragmentHostImpl implements FragmentHost {
+
+        @Override
+        public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
+            processingPendingEntranceTransition();
+        }
+
+        @Override
+        public void showTitleView(boolean show) {
+            if (mShowingHeaders && !isShowingTitle()) {
+                mPendingShowTitleView = true;
+            }
+            else if (!mShowingHeaders && !isShowingTitle()) {
+                showTitle(true);
+            }
+        }
+    }
+
+    /**
      * Interface that defines the interaction between {@link BrowseSupportFragment} and it's main
      * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
      * it will be used to get the fragment to be shown in the content section. Clients can
@@ -226,6 +273,7 @@
     public static class MainFragmentAdapter<T extends Fragment> {
         private boolean mScalingEnabled;
         private final T mFragment;
+        private FragmentHost mFragmentHost;
 
         public MainFragmentAdapter(T fragment) {
             this.mFragment = fragment;
@@ -293,6 +341,14 @@
         public void setScalingEnabled(boolean scalingEnabled) {
             this.mScalingEnabled = scalingEnabled;
         }
+
+        void setFragmentHost(FragmentHost fragmentHost) {
+            this.mFragmentHost = fragmentHost;
+        }
+
+        public final FragmentHost getFragmentHost() {
+            return mFragmentHost;
+        }
     }
 
     /**
@@ -387,6 +443,7 @@
             mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
             mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
                     .getAdapter(MainFragmentAdapter.class);
+            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
             if (!mIsPageRow) {
                 mMainFragmentRowsAdapter = (MainFragmentRowsAdapter) ((Adaptable)mMainFragment)
                         .getAdapter(MainFragmentRowsAdapter.class);
@@ -483,6 +540,7 @@
     private boolean mHeadersBackStackEnabled = true;
     private String mWithHeadersBackStackName;
     private boolean mShowingHeaders = true;
+    private boolean mPendingShowTitleView;
     private boolean mCanShowHeaders = true;
     private int mContainerListMarginStart;
     private int mContainerListAlignTop;
@@ -630,7 +688,7 @@
      * Sets an item clicked listener on the fragment.
      * OnItemViewClickedListener will override {@link View.OnClickListener} that
      * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
-     * So in general,  developer should choose one of the listeners but not both.
+     * So in general, developer should choose one of the listeners but not both.
      */
     public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
         mOnItemViewClickedListener = listener;
@@ -910,6 +968,7 @@
                 // This way we can maintain the invariant that mMainFragmentAdapter is never
                 // null and it avoids doing null checks all over the code.
                 mMainFragmentAdapter = new MainFragmentAdapter(null);
+                mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
             }
 
             ft.commit();
@@ -919,6 +978,7 @@
             mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
             mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
                     .getAdapter(MainFragmentAdapter.class);
+            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
 
             mIsPageRow = savedInstanceState != null ?
                     savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
@@ -993,7 +1053,7 @@
 
     @Override
     boolean isReadyForPrepareEntranceTransition() {
-        return mMainFragment != null;
+        return mMainFragment != null && mMainFragment.getView() != null;
     }
 
     @Override
@@ -1002,14 +1062,7 @@
     }
 
     void processingPendingEntranceTransition() {
-        // mMainFragment is not null at this point, it can perform prepare entrance transition.
         performPendingStates();
-        // mMainFragment's view is going to be created in next cycle
-        getView().post(new Runnable() {
-            public void run() {
-                performPendingStates();
-            }
-        });
     }
 
     private void createHeadersTransition() {
@@ -1041,6 +1094,15 @@
                             headerGridView.requestFocus();
                         }
                     }
+
+                    // Animate titleview once header animation is complete.
+                    if (!mShowingHeaders && mPendingShowTitleView) {
+                        mPendingShowTitleView = false;
+                        showTitle(true);
+                    } else if (mShowingHeaders && isShowingTitle() && mSelectedPosition != 0) {
+                        mPendingShowTitleView = true;
+                        showTitle(false);
+                    }
                 }
                 if (mBrowseTransitionListener != null) {
                     mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
@@ -1152,6 +1214,10 @@
             return;
         }
 
+        if (position != mSelectedPosition) {
+            mPendingShowTitleView = false;
+        }
+
         mHeadersSupportFragment.setSelectedPosition(position, smooth);
         replaceMainFragment(position);
 
@@ -1242,7 +1308,7 @@
         if (isEntranceTransitionEnabled()) {
             setEntranceTransitionStartState();
         }
-        processingPendingEntranceTransition();
+        showTitle(false);
     }
 
     private void onExpandTransitionStart(boolean expand, final Runnable callback) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
index 49781a6..5ac5b0d 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -21,7 +21,6 @@
 import android.app.FragmentManager.BackStackEntry;
 import android.app.FragmentTransaction;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
@@ -33,11 +32,9 @@
 import android.support.v17.leanback.widget.GuidedActionAdapter;
 import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
 import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.VerticalGridView;
 import android.support.v17.leanback.widget.ViewHolderTask;
 import android.support.v4.app.ActivityCompat;
 import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
@@ -45,14 +42,10 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -146,6 +139,8 @@
 
     private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
     private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
+    private static final String EXTRA_ACTION_PREFIX = "action_";
+    private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_";
 
     private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
 
@@ -441,7 +436,7 @@
      * than via XML.
      * @param fragmentManager The FragmentManager to be used in the transaction.
      * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
-     * @return The ID returned by the call FragmentTransaction.replace.
+     * @return The ID returned by the call FragmentTransaction.commit.
      */
     public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment) {
         return add(fragmentManager, fragment, android.R.id.content);
@@ -462,7 +457,7 @@
      * @param fragmentManager The FragmentManager to be used in the transaction.
      * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
      * @param id The id of container to add GuidedStepFragment, can be android.R.id.content.
-     * @return The ID returned by the call FragmentTransaction.replace.
+     * @return The ID returned by the call FragmentTransaction.commit.
      */
     public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment, int id) {
         GuidedStepFragment current = getCurrentGuidedStepFragment(fragmentManager);
@@ -590,7 +585,10 @@
 
     /**
      * Adds the specified GuidedStepFragment as content of Activity; no backstack entry is added so
-     * the activity will be dismissed when BACK key is pressed.
+     * the activity will be dismissed when BACK key is pressed.  The method is typically called in
+     * Activity.onCreate() when savedInstanceState is null.  When savedInstanceState is not null,
+     * the Activity is being restored,  do not call addAsRoot() to duplicate the Fragment restored
+     * by FragmentManager.
      * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
      *
      * Note: currently fragments added using this method must be created programmatically rather
@@ -598,12 +596,18 @@
      * @param activity The Activity to be used to insert GuidedstepFragment.
      * @param fragment The GuidedStepFragment to be inserted into the fragment stack.
      * @param id The id of container to add GuidedStepFragment, can be android.R.id.content.
-     * @return The ID returned by the call FragmentTransaction.replace.
+     * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already
+     *         GuidedStepFragment.
      */
     public static int addAsRoot(Activity activity, GuidedStepFragment fragment, int id) {
         // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
         activity.getWindow().getDecorView();
         FragmentManager fragmentManager = activity.getFragmentManager();
+        if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) {
+            Log.w(TAG, "Fragment is already exists, likely calling " +
+                    "addAsRoot() when savedInstanceState is not null in Activity.onCreate().");
+            return -1;
+        }
         FragmentTransaction ft = fragmentManager.beginTransaction();
         fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
         return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
@@ -957,9 +961,15 @@
         }
         ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
         onCreateActions(actions, savedInstanceState);
+        if (savedInstanceState != null) {
+            onRestoreActions(actions, savedInstanceState);
+        }
         setActions(actions);
         ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
         onCreateButtonActions(buttonActions, savedInstanceState);
+        if (savedInstanceState != null) {
+            onRestoreButtonActions(buttonActions, savedInstanceState);
+        }
         setButtonActions(buttonActions);
     }
 
@@ -1108,10 +1118,64 @@
     @Override
     public void onResume() {
         super.onResume();
-        if (mActionsStylist.isSubActionsExpanded()) {
-            mActionsStylist.getSubActionsGridView().requestFocus();
-        } else {
-            mActionsStylist.getActionsGridView().requestFocus();
+        getView().findViewById(R.id.action_fragment).requestFocus();
+    }
+
+    /**
+     * Get the key will be used to save GuidedAction with Fragment.
+     * @param action GuidedAction to get key.
+     * @return Key to save the GuidedAction.
+     */
+    final String getAutoRestoreKey(GuidedAction action) {
+        return EXTRA_ACTION_PREFIX + action.getId();
+    }
+
+    /**
+     * Get the key will be used to save GuidedAction with Fragment.
+     * @param action GuidedAction to get key.
+     * @return Key to save the GuidedAction.
+     */
+    final String getButtonAutoRestoreKey(GuidedAction action) {
+        return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
+    }
+
+    final static boolean isSaveEnabled(GuidedAction action) {
+        return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
+    }
+
+    final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onSaveActions(List<GuidedAction> actions, Bundle outState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onSaveInstanceState(outState, getAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action));
+            }
         }
     }
 
@@ -1121,6 +1185,8 @@
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
+        onSaveActions(mActions, outState);
+        onSaveButtonActions(mButtonActions, outState);
         outState.putInt(EXTRA_ACTION_SELECTED_INDEX,
                 (mActionsStylist.getActionsGridView() != null) ?
                         getSelectedActionPosition() : mSelectedIndex);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index 7e6b328..647d1b2 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -23,7 +23,6 @@
 import android.support.v4.app.FragmentManager.BackStackEntry;
 import android.support.v4.app.FragmentTransaction;
 import android.content.Context;
-import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
@@ -35,11 +34,9 @@
 import android.support.v17.leanback.widget.GuidedActionAdapter;
 import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
 import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.VerticalGridView;
 import android.support.v17.leanback.widget.ViewHolderTask;
 import android.support.v4.app.ActivityCompat;
 import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
@@ -47,14 +44,10 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 /**
@@ -148,6 +141,8 @@
 
     private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepSupportFragment";
     private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
+    private static final String EXTRA_ACTION_PREFIX = "action_";
+    private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_";
 
     private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
 
@@ -443,7 +438,7 @@
      * than via XML.
      * @param fragmentManager The FragmentManager to be used in the transaction.
      * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
-     * @return The ID returned by the call FragmentTransaction.replace.
+     * @return The ID returned by the call FragmentTransaction.commit.
      */
     public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment) {
         return add(fragmentManager, fragment, android.R.id.content);
@@ -464,7 +459,7 @@
      * @param fragmentManager The FragmentManager to be used in the transaction.
      * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
      * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
-     * @return The ID returned by the call FragmentTransaction.replace.
+     * @return The ID returned by the call FragmentTransaction.commit.
      */
     public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment, int id) {
         GuidedStepSupportFragment current = getCurrentGuidedStepSupportFragment(fragmentManager);
@@ -592,7 +587,10 @@
 
     /**
      * Adds the specified GuidedStepSupportFragment as content of Activity; no backstack entry is added so
-     * the activity will be dismissed when BACK key is pressed.
+     * the activity will be dismissed when BACK key is pressed.  The method is typically called in
+     * Activity.onCreate() when savedInstanceState is null.  When savedInstanceState is not null,
+     * the Activity is being restored,  do not call addAsRoot() to duplicate the Fragment restored
+     * by FragmentManager.
      * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
      *
      * Note: currently fragments added using this method must be created programmatically rather
@@ -600,12 +598,18 @@
      * @param activity The Activity to be used to insert GuidedstepFragment.
      * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
      * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
-     * @return The ID returned by the call FragmentTransaction.replace.
+     * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already
+     *         GuidedStepSupportFragment.
      */
     public static int addAsRoot(FragmentActivity activity, GuidedStepSupportFragment fragment, int id) {
         // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
         activity.getWindow().getDecorView();
         FragmentManager fragmentManager = activity.getSupportFragmentManager();
+        if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) {
+            Log.w(TAG, "Fragment is already exists, likely calling " +
+                    "addAsRoot() when savedInstanceState is not null in Activity.onCreate().");
+            return -1;
+        }
         FragmentTransaction ft = fragmentManager.beginTransaction();
         fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
         return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
@@ -959,9 +963,15 @@
         }
         ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
         onCreateActions(actions, savedInstanceState);
+        if (savedInstanceState != null) {
+            onRestoreActions(actions, savedInstanceState);
+        }
         setActions(actions);
         ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
         onCreateButtonActions(buttonActions, savedInstanceState);
+        if (savedInstanceState != null) {
+            onRestoreButtonActions(buttonActions, savedInstanceState);
+        }
         setButtonActions(buttonActions);
     }
 
@@ -1110,10 +1120,64 @@
     @Override
     public void onResume() {
         super.onResume();
-        if (mActionsStylist.isSubActionsExpanded()) {
-            mActionsStylist.getSubActionsGridView().requestFocus();
-        } else {
-            mActionsStylist.getActionsGridView().requestFocus();
+        getView().findViewById(R.id.action_fragment).requestFocus();
+    }
+
+    /**
+     * Get the key will be used to save GuidedAction with Fragment.
+     * @param action GuidedAction to get key.
+     * @return Key to save the GuidedAction.
+     */
+    final String getAutoRestoreKey(GuidedAction action) {
+        return EXTRA_ACTION_PREFIX + action.getId();
+    }
+
+    /**
+     * Get the key will be used to save GuidedAction with Fragment.
+     * @param action GuidedAction to get key.
+     * @return Key to save the GuidedAction.
+     */
+    final String getButtonAutoRestoreKey(GuidedAction action) {
+        return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
+    }
+
+    final static boolean isSaveEnabled(GuidedAction action) {
+        return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
+    }
+
+    final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onSaveActions(List<GuidedAction> actions, Bundle outState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onSaveInstanceState(outState, getAutoRestoreKey(action));
+            }
+        }
+    }
+
+    final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) {
+        for (int i = 0, size = actions.size(); i < size; i++) {
+            GuidedAction action = actions.get(i);
+            if (isSaveEnabled(action)) {
+                action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action));
+            }
         }
     }
 
@@ -1123,6 +1187,8 @@
     @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
+        onSaveActions(mActions, outState);
+        onSaveButtonActions(mButtonActions, outState);
         outState.putInt(EXTRA_ACTION_SELECTED_INDEX,
                 (mActionsStylist.getActionsGridView() != null) ?
                         getSelectedActionPosition() : mSelectedIndex);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java b/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
index bc0fee7..e05ca0c 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
@@ -28,22 +28,22 @@
     private Runnable runnable = new Runnable() {
         @Override
         public void run() {
-            if (!mEnableProgressBar || mIsShowing ||
-                    (!mUserProvidedProgressBar && rootView == null)) {
+            if (!mEnableProgressBar || (!mUserProvidedProgressBar && rootView == null)) {
                 return;
             }
 
-            mIsShowing = true;
-            if (mProgressBarView == null) {
-                mProgressBarView = new ProgressBar(
-                        rootView.getContext(), null, android.R.attr.progressBarStyleLarge);
-                FrameLayout.LayoutParams progressBarParams = new FrameLayout.LayoutParams(
-                        FrameLayout.LayoutParams.WRAP_CONTENT,
-                        FrameLayout.LayoutParams.WRAP_CONTENT);
-                progressBarParams.gravity = Gravity.CENTER;
-                rootView.addView(mProgressBarView, progressBarParams);
-            } else if (mUserProvidedProgressBar) {
-                mProgressBarView.setVisibility(View.VISIBLE);
+            if (mIsShowing) {
+                if (mProgressBarView == null) {
+                    mProgressBarView = new ProgressBar(
+                            rootView.getContext(), null, android.R.attr.progressBarStyleLarge);
+                    FrameLayout.LayoutParams progressBarParams = new FrameLayout.LayoutParams(
+                            FrameLayout.LayoutParams.WRAP_CONTENT,
+                            FrameLayout.LayoutParams.WRAP_CONTENT);
+                    progressBarParams.gravity = Gravity.CENTER;
+                    rootView.addView(mProgressBarView, progressBarParams);
+                } else if (mUserProvidedProgressBar) {
+                    mProgressBarView.setVisibility(View.VISIBLE);
+                }
             }
         }
     };
@@ -64,6 +64,7 @@
      */
     public void show() {
         if (mEnableProgressBar) {
+            mIsShowing = true;
             mHandler.postDelayed(runnable, mInitialDelay);
         }
     }
@@ -72,10 +73,6 @@
      * Hides the progress bar.
      */
     public void hide() {
-        if (!mIsShowing) {
-            return;
-        }
-
         mIsShowing = false;
         if (mUserProvidedProgressBar) {
             mProgressBarView.setVisibility(View.INVISIBLE);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
index 2294903..7721be2 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -51,14 +51,20 @@
  */
 public class RowsFragment extends BaseRowFragment implements Adaptable {
 
-    final MainFragmentAdapter mMainFragmentAdapter = new MainFragmentAdapter(this);
-    final MainFragmentRowsAdapter mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+    private MainFragmentAdapter mMainFragmentAdapter;
+    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
 
     @Override
     public Object getAdapter(Class clazz) {
         if (clazz == BrowseFragment.MainFragmentAdapter.class) {
+            if (mMainFragmentAdapter == null) {
+                mMainFragmentAdapter = new MainFragmentAdapter(this);
+            }
             return mMainFragmentAdapter;
         } else if (clazz == BrowseFragment.MainFragmentRowsAdapter.class) {
+            if (mMainFragmentRowsAdapter == null) {
+                mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+            }
             return mMainFragmentRowsAdapter;
         }
         return null;
@@ -281,6 +287,9 @@
 
         mRecycledViewPool = null;
         mPresenterMapper = null;
+        if (mMainFragmentAdapter != null) {
+            mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+        }
     }
 
     @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
index 197aad2..27ec54b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -53,14 +53,20 @@
  */
 public class RowsSupportFragment extends BaseRowSupportFragment implements Adaptable {
 
-    final MainFragmentAdapter mMainFragmentAdapter = new MainFragmentAdapter(this);
-    final MainFragmentRowsAdapter mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+    private MainFragmentAdapter mMainFragmentAdapter;
+    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
 
     @Override
     public Object getAdapter(Class clazz) {
         if (clazz == BrowseSupportFragment.MainFragmentAdapter.class) {
+            if (mMainFragmentAdapter == null) {
+                mMainFragmentAdapter = new MainFragmentAdapter(this);
+            }
             return mMainFragmentAdapter;
         } else if (clazz == BrowseSupportFragment.MainFragmentRowsAdapter.class) {
+            if (mMainFragmentRowsAdapter == null) {
+                mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+            }
             return mMainFragmentRowsAdapter;
         }
         return null;
@@ -283,6 +289,9 @@
 
         mRecycledViewPool = null;
         mPresenterMapper = null;
+        if (mMainFragmentAdapter != null) {
+            mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+        }
     }
 
     @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
new file mode 100644
index 0000000..eac61a8
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
@@ -0,0 +1,167 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v17.leanback.R;
+import android.view.ContextThemeWrapper;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Abstract {@link Presenter} class for rendering media items in a playlist format.
+ * Media items in the playlist are arranged as a vertical list with each row holding each media's
+ * metadata which is provided by the user of this class.
+ * <p>
+ *     Subclasses must override {@link #onBindMediaViewHolder} to implement their media item model
+ *     data binding to each row view
+ * </p>
+ * <p>
+ *     {@link AbstractMediaListHeaderPresenter} can be used in conjunction with this presenter in
+ *     order to display a playlist with a header view..
+ * </p>
+ */
+public abstract class AbstractMediaItemPresenter extends RowPresenter {
+
+
+    private int mBackgroundColor = Color.TRANSPARENT;
+    private boolean mBackgroundColorSet;
+    private final Context mContext;
+
+    /**
+     * Constructor used for creating an abstract media item presenter of a given theme.
+     * @param context The context the user of this presenter is running in.
+     * @param mThemeResId The resource id of the desired theme used for styling of this presenter.
+     */
+    public AbstractMediaItemPresenter(Context context, int mThemeResId) {
+        mContext = new ContextThemeWrapper(context.getApplicationContext(), mThemeResId);
+        setHeaderPresenter(null);
+    }
+
+    /**
+     * Constructor used for creating an abstract media item presenter.
+     * The styling for this presenter is extracted from Context of parent in
+     * {@link #createRowViewHolder(ViewGroup)}.
+     */
+    public AbstractMediaItemPresenter() {
+        mContext = null;
+        setHeaderPresenter(null);
+    }
+
+    /**
+     * The ViewHolder for the {@link AbstractMediaItemPresenter}. It references different views
+     * that place different meta-data corresponding to a media item.
+     */
+    public static class ViewHolder extends RowPresenter.ViewHolder {
+
+        private final View mContainerView;
+        private final TextView mTrackNumberView;
+        private final TextView mTrackNameView;
+        private final TextView mTrackDurationView;
+
+        View.OnClickListener mOnClickListener = new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (getOnItemViewClickedListener() != null) {
+                    getOnItemViewClickedListener().onItemClicked(ViewHolder.this,
+                            ViewHolder.this.getRowObject(), ViewHolder.this,
+                            ViewHolder.this.getRowObject());
+                }
+            }
+        };
+
+        public ViewHolder(View view) {
+            super(view);
+            mContainerView  = view.findViewById(R.id.rowContainer);
+            mContainerView.setOnClickListener(mOnClickListener);
+            mTrackNumberView = (TextView) view.findViewById(R.id.trackNumber);
+            mTrackNameView = (TextView) view.findViewById(R.id.trackName);
+            mTrackDurationView = (TextView) view.findViewById(R.id.trackDuration);
+        }
+
+        /**
+         * @return The TextView responsible for rendering the track number
+         */
+        public TextView getTrackNumberView() {
+            return mTrackNumberView;
+        }
+
+        /**
+         * @return The TextView responsible for rendering the track name
+         */
+        public TextView getTrackNameView() {
+            return mTrackNameView;
+        }
+
+        /**
+         * @return The TextView responsible for rendering the track duration
+         */
+        public TextView getTrackDurationView() {
+            return mTrackDurationView;
+        }
+    }
+
+    @Override
+    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+        Context context = mContext != null ? mContext : parent.getContext();
+        View view = LayoutInflater.from(context).
+                inflate(R.layout.lb_row_media_item, parent, false);
+        ViewHolder vh = new ViewHolder(view);
+        if (mBackgroundColorSet) {
+            vh.mContainerView.setBackgroundColor(mBackgroundColor);
+        }
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public boolean isUsingDefaultSelectEffect() {
+        return false;
+    }
+
+    @Override
+    protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
+        super.onBindRowViewHolder(vh, item);
+        onBindMediaViewHolder((ViewHolder) vh, item);
+    }
+
+    /**
+     * Sets the background color for the row views within the playlist.
+     * If this is not set, a default color, defaultBrandColor, from theme is used.
+     * This defaultBrandColor defaults to android:attr/colorPrimary on v21, if it's specified.
+     * @param color The ARGB color used to set as the media list background color.
+     */
+    public void setBackgroundColor(int color) {
+        mBackgroundColorSet = true;
+        mBackgroundColor = color;
+    }
+
+    /**
+     * Binds the media data model provided by the user to the {@link ViewHolder} provided by the
+     * {@link AbstractMediaItemPresenter}.
+     * The subclasses of this presenter can access and bind individual views for TrackNumber,
+     * TrackName, and TrackDuration, by calling {@link ViewHolder#getTrackNumberView},
+     * {@link ViewHolder#getTrackNameView}, and {@link ViewHolder#getTrackDurationView}, on the
+     * {@link ViewHolder} provided as the argument {@code vh} by this presenter.
+     *
+     * @param vh The ViewHolder for this {@link AbstractMediaItemPresenter}.
+     * @param item The media item data object being presented.
+     */
+    protected abstract void onBindMediaViewHolder(ViewHolder vh, Object item);
+
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java
new file mode 100644
index 0000000..9809fa1
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java
@@ -0,0 +1,135 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.support.v17.leanback.R;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Abstract presenter class for rendering the header for a list of tracks in a playlist.
+ * The presenter creates a {@link ViewHolder} for the TextView holding the header text.
+ * <p>
+ *    Subclasses of this class must override {@link
+ *    #onBindMediaListHeaderViewHolder(ViewHolder, Object)} in order to bind their header text to
+ *    the tracklist header view.
+ * </p>
+ * <p>
+ * {@link AbstractMediaItemPresenter} can be used in conjunction with this presenter in order to
+ * display a playlist with a header view.
+ * </p>
+ */
+public abstract class AbstractMediaListHeaderPresenter extends RowPresenter{
+
+    private final Context mContext;
+    private int mBackgroundColor = Color.TRANSPARENT;
+    private boolean mBackgroundColorSet;
+
+    /**
+     * The ViewHolder for the {@link AbstractMediaListHeaderPresenter}. It references the TextView
+     * that places the header text provided by the data binder.
+     */
+    public static class ViewHolder extends RowPresenter.ViewHolder {
+
+        private final TextView mHeaderView;
+
+        public ViewHolder(View view) {
+            super(view);
+            mHeaderView = (TextView) view.findViewById(R.id.trackListHeader);
+        }
+
+        /**
+         *
+         * @return the header {@link TextView} responsible for rendering the playlist header text.
+         */
+        public TextView getHeaderView() {
+            return mHeaderView;
+        }
+    }
+
+    /**
+     * Constructor used for creating an abstract media-list header presenter of a given theme.
+     * @param context The context the user of this presenter is running in.
+     * @param mThemeResId The resource id of the desired theme used for styling of this presenter.
+     */
+    public AbstractMediaListHeaderPresenter(Context context, int mThemeResId) {
+        mContext = new ContextThemeWrapper(context.getApplicationContext(), mThemeResId);
+        setHeaderPresenter(null);
+    }
+
+    /**
+     * Constructor used for creating an abstract media-list header presenter.
+     * The styling for this presenter is extracted from Context of parent in
+     * {@link #createRowViewHolder(ViewGroup)}.
+     */
+    public AbstractMediaListHeaderPresenter() {
+        mContext = null;
+        setHeaderPresenter(null);
+    }
+
+    @Override
+    public boolean isUsingDefaultSelectEffect() {
+        return false;
+    }
+
+    @Override
+    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+        Context context = (mContext != null) ? mContext : parent.getContext();
+        View view = LayoutInflater.from(context).inflate(R.layout.lb_row_track_list_header,
+                parent, false);
+        view.setFocusable(false);
+        view.setFocusableInTouchMode(false);
+        ViewHolder vh = new ViewHolder(view);
+        if (mBackgroundColorSet) {
+            vh.view.setBackgroundColor(mBackgroundColor);
+        }
+        return vh;
+    }
+
+    @Override
+    protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
+        super.onBindRowViewHolder(vh, item);
+        onBindMediaListHeaderViewHolder((ViewHolder) vh, item);
+    }
+
+    /**
+     * Sets the background color for the row views within the playlist.
+     * If this is not set, a default color, defaultBrandColor, from theme is used.
+     * This defaultBrandColor defaults to android:attr/colorPrimary on v21, if it's specified.
+     * @param color The ARGB color used to set as the header text background color.
+     */
+    public void setBackgroundColor(int color) {
+        mBackgroundColorSet = true;
+        mBackgroundColor = color;
+    }
+
+    /**
+     * Binds the playlist header data model provided by the user to the {@link ViewHolder}
+     * provided by the {@link AbstractMediaListHeaderPresenter}.
+     * The subclasses of this presenter can access and bind the text view corresponding to the
+     * header by calling {@link ViewHolder#getHeaderView()}, on the
+     * {@link ViewHolder} provided as the argument {@code vh} by this presenter.
+     *
+     * @param vh The ViewHolder for this {@link AbstractMediaListHeaderPresenter}.
+     * @param item The header data object being presented.
+     */
+    protected abstract void onBindMediaListHeaderViewHolder(ViewHolder vh, Object item);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Action.java b/v17/leanback/src/android/support/v17/leanback/widget/Action.java
index 7bb696a..5e6e313 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Action.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Action.java
@@ -19,14 +19,15 @@
 
 import java.util.ArrayList;
 
-import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
-
 /**
  * An action contains one or two lines of text, an optional image and an optional id. It may also
  * be invoked by one or more keycodes.
  */
 public class Action {
 
+    /** Indicates that an id has not been set. */
+    public static final long NO_ID = -1;
+
     private long mId = NO_ID;
     private Drawable mIcon;
     private CharSequence mLabel1;
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 56f81fe..33c764a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -23,6 +23,7 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.OrientationHelper;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView.Recycler;
 import android.support.v7.widget.RecyclerView.State;
@@ -123,32 +124,6 @@
             return view.getHeight() - mTopInset - mBottomInset;
         }
 
-        int getDecoratedOpticalLeftWithMargin(RecyclerView.LayoutManager lm, View view) {
-            return lm.getDecoratedLeft(view) + mLeftInset - leftMargin;
-        }
-
-        int getDecoratedOpticalTopWithMargin(RecyclerView.LayoutManager lm, View view) {
-            return lm.getDecoratedTop(view) + mTopInset - topMargin;
-        }
-
-        int getDecoratedOpticalRightWithMargin(RecyclerView.LayoutManager lm, View view) {
-            return lm.getDecoratedRight(view) - mRightInset + rightMargin;
-        }
-
-        int getDecoratedOpticalBottomWithMargin(RecyclerView.LayoutManager lm, View view) {
-            return lm.getDecoratedBottom(view) - mBottomInset + bottomMargin;
-        }
-
-        int getDecoratedOpticalWidthWithMargin(RecyclerView.LayoutManager lm, View view) {
-            return lm.getDecoratedRight(view) - lm.getDecoratedLeft(view)
-                    - mLeftInset - mRightInset + leftMargin + rightMargin;
-        }
-
-        int getDecoratedOpticalHeightWithMargin(RecyclerView.LayoutManager lm, View view) {
-            return lm.getDecoratedBottom(view) - lm.getDecoratedTop(view)
-                    - mTopInset - mBottomInset + topMargin + bottomMargin;
-        }
-
         int getOpticalLeftInset() {
             return mLeftInset;
         }
@@ -433,6 +408,7 @@
      * The orientation of a "row".
      */
     private int mOrientation = HORIZONTAL;
+    private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
 
     private RecyclerView.State mState;
     private RecyclerView.Recycler mRecycler;
@@ -670,6 +646,7 @@
         }
 
         mOrientation = orientation;
+        mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
         mWindowAlignment.setOrientation(orientation);
         mItemAlignment.setOrientation(orientation);
         mForceFullLayout = true;
@@ -1009,22 +986,49 @@
         return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
     }
 
+    @Override
+    public int getDecoratedLeft(View child) {
+        return super.getDecoratedLeft(child) + ((LayoutParams) child.getLayoutParams()).mLeftInset;
+    }
+
+    @Override
+    public int getDecoratedTop(View child) {
+        return super.getDecoratedTop(child) + ((LayoutParams) child.getLayoutParams()).mTopInset;
+    }
+
+    @Override
+    public int getDecoratedRight(View child) {
+        return super.getDecoratedRight(child) -
+                ((LayoutParams) child.getLayoutParams()).mRightInset;
+    }
+
+    @Override
+    public int getDecoratedBottom(View child) {
+        return super.getDecoratedBottom(child) -
+                ((LayoutParams) child.getLayoutParams()).mBottomInset;
+    }
+
+    @Override
+    public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
+        super.getDecoratedBoundsWithMargins(view, outBounds);
+        LayoutParams params = ((LayoutParams) view.getLayoutParams());
+        outBounds.left += params.mLeftInset;
+        outBounds.top += params.mTopInset;
+        outBounds.right -= params.mRightInset;
+        outBounds.bottom -= params.mBottomInset;
+    }
+
     private int getViewMin(View v) {
-        LayoutParams lp = (LayoutParams) v.getLayoutParams();
-        return (mOrientation == HORIZONTAL) ? lp.getDecoratedOpticalLeftWithMargin(this, v)
-                : lp.getDecoratedOpticalTopWithMargin(this, v);
+        return mOrientationHelper.getDecoratedStart(v);
     }
 
     private int getViewMax(View v) {
-        LayoutParams lp = (LayoutParams) v.getLayoutParams();
-        return (mOrientation == HORIZONTAL) ? lp.getDecoratedOpticalRightWithMargin(this, v)
-                : lp.getDecoratedOpticalBottomWithMargin(this, v);
+        return mOrientationHelper.getDecoratedEnd(v);
     }
 
     private int getViewPrimarySize(View view) {
-        LayoutParams p = (LayoutParams) view.getLayoutParams();
-        return mOrientation == HORIZONTAL ? p.getDecoratedOpticalWidthWithMargin(this, view)
-                : p.getDecoratedOpticalHeightWithMargin(this, view);
+        getDecoratedBoundsWithMargins(view, sTempRect);
+        return mOrientation == HORIZONTAL ? sTempRect.width() : sTempRect.height();
     }
 
     private int getViewCenter(View view) {
@@ -1294,6 +1298,27 @@
         }
      };
 
+    private final Runnable mAskFocusRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (hasFocus()) {
+                return;
+            }
+            View view = findViewByPosition(mFocusPosition);
+            if (view != null && view.hasFocusable()) {
+                mBaseGridView.focusableViewAvailable(view);
+                return;
+            }
+            for (int i = 0, count = getChildCount(); i < count; i++) {
+                view = getChildAt(i);
+                if (view != null && view.hasFocusable()) {
+                    mBaseGridView.focusableViewAvailable(view);
+                    break;
+                }
+            }
+        }
+    };
+
     @Override
     public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
         saveContext(recycler, state);
@@ -1623,19 +1648,13 @@
             right = startSecondary + sizeSecondary;
         }
         LayoutParams params = (LayoutParams) v.getLayoutParams();
-        // layoutDecorated() doesn't handle margins, so we need exclude margin:
-        int decoratedLeftExcludeMargin = left + params.leftMargin;
-        int decoratedTopExcludeMargin = top + params.topMargin;
-        int decoratedRightExcludeMargin = right - params.rightMargin;
-        int decoratedBottomExcludeMargin = bottom - params.bottomMargin;
-        layoutDecorated(v, decoratedLeftExcludeMargin, decoratedTopExcludeMargin,
-                decoratedRightExcludeMargin, decoratedBottomExcludeMargin);
-        // Now v.getLeft() includes the extra space for optical bounds, subtracting it from value
-        // passed in layoutDecorated(), we can get the optical bounds insets.
-        params.setOpticalInsets(decoratedLeftExcludeMargin - getDecoratedLeft(v),
-                decoratedTopExcludeMargin - getDecoratedTop(v),
-                getDecoratedRight(v) - decoratedRightExcludeMargin,
-                getDecoratedBottom(v) - decoratedBottomExcludeMargin);
+        layoutDecoratedWithMargins(v, left, top, right, bottom);
+        // Now super.getDecoratedBoundsWithMargins() includes the extra space for optical bounds,
+        // subtracting it from value passed in layoutDecoratedWithMargins(), we can get the optical
+        // bounds insets.
+        super.getDecoratedBoundsWithMargins(v, sTempRect);
+        params.setOpticalInsets(left - sTempRect.left, top - sTempRect.top,
+                sTempRect.right - right, sTempRect.bottom - bottom);
         updateChildAlignments(v);
         if (TRACE) TraceHelper.endSection();
     }
@@ -1934,6 +1953,9 @@
 
         mInLayout = false;
         leaveContext();
+        if (!hadFocus && !mInFastRelayout && mBaseGridView.hasFocusable()) {
+            ViewCompat.postOnAnimation(mBaseGridView, mAskFocusRunnable);
+        }
         if (DEBUG) Log.v(getTag(), "layoutChildren end");
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
index 30b3b89..2a39323 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
@@ -13,6 +13,7 @@
  */
 package android.support.v17.leanback.widget;
 
+import android.os.Bundle;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.StringRes;
 import android.support.v17.leanback.R;
@@ -137,7 +138,7 @@
          */
         public BuilderBase(Context context) {
             mContext = context;
-            mActionFlags = PF_ENABLED | PF_FOCUSABLE;
+            mActionFlags = PF_ENABLED | PF_FOCUSABLE | PF_AUTORESTORE;
         }
 
         /**
@@ -526,6 +527,19 @@
             mSubActions = subActions;
             return (B) this;
         }
+
+        /**
+         * Explicitly sets auto restore feature on the GuidedAction.  It's by default true.
+         * @param autoSaveRestoreEnanbled True if turn on auto save/restore of GuidedAction content,
+         *                                false otherwise.
+         * @return The same BuilderBase object.
+         * @see GuidedAction#isAutoSaveRestoreEnabled()
+         */
+        public B autoSaveRestoreEnabled(boolean autoSaveRestoreEnanbled) {
+            setFlags(autoSaveRestoreEnanbled ? PF_AUTORESTORE : 0, PF_AUTORESTORE);
+            return (B) this;
+        }
+
     }
 
     /**
@@ -567,6 +581,7 @@
     private static final int PF_INFO_ONLY = 0x00000008;
     private static final int PF_ENABLED = 0x00000010;
     private static final int PF_FOCUSABLE = 0x00000020;
+    private static final int PF_AUTORESTORE = 0x00000040;
     private int mActionFlags;
 
     private CharSequence mEditTitle;
@@ -860,4 +875,81 @@
         return mSubActions != null;
     }
 
+    /**
+     * Returns true if Action will be saved to instanceState and restored later, false otherwise.
+     * The default value is true.  When isAutoSaveRestoreEnabled() is true and {@link #getId()} is
+     * not {@link #NO_ID}:
+     * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
+     * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
+     * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
+     * <li>{@link GuidedDatePickerAction} will be saved</li>
+     * App may explicitly disable auto restore and handle by itself. App should override Fragment
+     * onSaveInstanceState() and onCreateActions()
+     * @return True if Action will be saved to instanceState and restored later, false otherwise.
+     */
+    public final boolean isAutoSaveRestoreEnabled() {
+        return (mActionFlags & PF_AUTORESTORE) == PF_AUTORESTORE;
+    }
+
+    /**
+     * Save action into a bundle using a given key. When isAutoRestoreEna() is true:
+     * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
+     * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
+     * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
+     * <li>{@link GuidedDatePickerAction} will be saved</li>
+     * Subclass may override this method.
+     * @param bundle  Bundle to save the Action.
+     * @param key Key used to save the Action.
+     */
+    public void onSaveInstanceState(Bundle bundle, String key) {
+        if (needAutoSaveTitle() && getTitle() != null) {
+            bundle.putString(key, getTitle().toString());
+        } else if (needAutoSaveDescription() && getDescription() != null) {
+            bundle.putString(key, getDescription().toString());
+        } else if (getCheckSetId() != NO_CHECK_SET) {
+            bundle.putBoolean(key, isChecked());
+        }
+    }
+
+    /**
+     * Restore action from a bundle using a given key. When isAutoRestore() is true:
+     * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
+     * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
+     * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
+     * <li>{@link GuidedDatePickerAction} will be saved</li>
+     * Subclass may override this method.
+     * @param bundle  Bundle to restore the Action from.
+     * @param key Key used to restore the Action.
+     */
+    public void onRestoreInstanceState(Bundle bundle, String key) {
+        if (needAutoSaveTitle()) {
+            String title = bundle.getString(key);
+            if (title != null) {
+                setTitle(title);
+            }
+        } else if (needAutoSaveDescription()) {
+            String description = bundle.getString(key);
+            if (description != null) {
+                setDescription(description);
+            }
+        } else if (getCheckSetId() != NO_CHECK_SET) {
+            setChecked(bundle.getBoolean(key, isChecked()));
+        }
+    }
+
+    final static boolean isPasswordVariant(int inputType) {
+        final int variantion = inputType & InputType.TYPE_MASK_VARIATION;
+        return variantion == InputType.TYPE_TEXT_VARIATION_PASSWORD
+                || variantion == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+                || variantion == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
+    }
+
+    final boolean needAutoSaveTitle() {
+        return isEditable() && !isPasswordVariant(getEditInputType());
+    }
+
+    final boolean needAutoSaveDescription() {
+        return isDescriptionEditable() && !isPasswordVariant(getDescriptionEditInputType());
+    }
+
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
index 713f716..2689f46 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
@@ -417,6 +417,8 @@
                         R.id.guidedactions_sub_list);
             }
         }
+        mActionsGridView.setFocusable(false);
+        mActionsGridView.setFocusableInTouchMode(false);
 
         // Cache widths, chevron alpha values, max and min text lines, etc
         Context ctx = mMainView.getContext();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
index 67bf28a..9b9f29f 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
@@ -14,6 +14,7 @@
 package android.support.v17.leanback.widget;
 
 import android.content.Context;
+import android.os.Bundle;
 import android.support.v17.leanback.widget.picker.DatePicker;
 
 import java.util.Calendar;
@@ -175,4 +176,14 @@
     public long getMaxDate() {
         return mMaxDate;
     }
+
+    @Override
+    public void onSaveInstanceState(Bundle bundle, String key) {
+        bundle.putLong(key, getDate());
+    }
+
+    @Override
+    public void onRestoreInstanceState(Bundle bundle, String key) {
+        setDate(bundle.getLong(key, getDate()));
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java b/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
index 2148ddd..94ab8d8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
@@ -17,8 +17,9 @@
 package android.support.v17.leanback.widget;
 
 import android.animation.Animator;
-import android.animation.AnimatorInflater;
 import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -33,16 +34,59 @@
 import android.support.annotation.VisibleForTesting;
 import android.support.v17.leanback.R;
 import android.util.AttributeSet;
+import android.util.Property;
 import android.view.View;
-
-import java.util.ArrayList;
-import java.util.List;
+import android.view.animation.DecelerateInterpolator;
 
 /**
  * A page indicator with dots.
  * @hide
  */
 public class PagingIndicator extends View {
+    private static final long DURATION_ALPHA = 167;
+    private static final long DURATION_DIAMETER = 417;
+    private static final long DURATION_TRANSLATION_X = DURATION_DIAMETER;
+    private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
+
+    private static final Property<Dot, Float> DOT_ALPHA
+            = new Property<Dot, Float>(Float.class, "alpha") {
+        @Override
+        public Float get(Dot dot) {
+            return dot.getAlpha();
+        }
+
+        @Override
+        public void set(Dot dot, Float value) {
+            dot.setAlpha(value);
+        }
+    };
+
+    private static final Property<Dot, Float> DOT_DIAMETER
+            = new Property<Dot, Float>(Float.class, "diameter") {
+        @Override
+        public Float get(Dot dot) {
+            return dot.getDiameter();
+        }
+
+        @Override
+        public void set(Dot dot, Float value) {
+            dot.setDiameter(value);
+        }
+    };
+
+    private static final Property<Dot, Float> DOT_TRANSLATION_X
+            = new Property<Dot, Float>(Float.class, "translation_x") {
+        @Override
+        public Float get(Dot dot) {
+            return dot.getTranslationX();
+        }
+
+        @Override
+        public void set(Dot dot, Float value) {
+            dot.setTranslationX(value);
+        }
+    };
+
     // attribute
     private boolean mIsLtr;
     private final int mDotDiameter;
@@ -71,8 +115,8 @@
     private final int mDotFgSelectColor;
     private final Paint mBgPaint;
     private final Paint mFgPaint;
-    private final Animator mShowAnimator;
-    private final Animator mHideAnimator;
+    private final AnimatorSet mShowAnimator;
+    private final AnimatorSet mHideAnimator;
     private final AnimatorSet mAnimator = new AnimatorSet();
     private Bitmap mArrow;
     private final Rect mArrowRect;
@@ -91,23 +135,26 @@
         Resources res = getResources();
         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PagingIndicator,
                 defStyle, 0);
-        int bgColor = res.getColor(R.color.lb_page_indicator_dot);
-        try {
-            bgColor = typedArray.getColor(R.styleable.PagingIndicator_dotBgColor, bgColor);
-        } finally {
-            typedArray.recycle();
-        }
-        mIsLtr = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
-        mDotRadius = res.getDimensionPixelSize(R.dimen.lb_page_indicator_dot_radius);
+        mDotRadius = getDimensionFromTypedArray(typedArray, R.styleable.PagingIndicator_dotRadius,
+                R.dimen.lb_page_indicator_dot_radius);
         mDotDiameter = mDotRadius * 2;
-        mDotGap = res.getDimensionPixelSize(R.dimen.lb_page_indicator_dot_gap);
-        mArrowGap = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_gap);
-        mArrowDiameter = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_diameter);
-        mArrowRadius = mArrowDiameter / 2;
+        mArrowRadius = getDimensionFromTypedArray(typedArray,
+                R.styleable.PagingIndicator_arrowRadius, R.dimen.lb_page_indicator_arrow_radius);
+        mArrowDiameter = mArrowRadius * 2;
+        mDotGap = getDimensionFromTypedArray(typedArray, R.styleable.PagingIndicator_dotToDotGap,
+                R.dimen.lb_page_indicator_dot_gap);
+        mArrowGap = getDimensionFromTypedArray(typedArray,
+                R.styleable.PagingIndicator_dotToArrowGap, R.dimen.lb_page_indicator_arrow_gap);
+        int bgColor = getColorFromTypedArray(typedArray, R.styleable.PagingIndicator_dotBgColor,
+                R.color.lb_page_indicator_dot);
         mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mDotFgSelectColor = res.getColor(R.color.lb_page_indicator_arrow_background);
-        int shadowColor = res.getColor(R.color.lb_page_indicator_arrow_shadow);
         mBgPaint.setColor(bgColor);
+        mDotFgSelectColor = getColorFromTypedArray(typedArray,
+                R.styleable.PagingIndicator_arrowBgColor,
+                R.color.lb_page_indicator_arrow_background);
+        typedArray.recycle();
+        mIsLtr = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+        int shadowColor = res.getColor(R.color.lb_page_indicator_arrow_shadow);
         mShadowRadius = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_shadow_radius);
         mFgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         int shadowOffset = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_shadow_offset);
@@ -116,27 +163,26 @@
         mArrowRect = new Rect(0, 0, mArrow.getWidth(), mArrow.getHeight());
         mArrowToBgRatio = (float) mArrow.getWidth() / (float) mArrowDiameter;
         // Initialize animations.
-        List<Animator> animators = new ArrayList<>();
-        mShowAnimator = AnimatorInflater.loadAnimator(context,
-                R.animator.lb_page_indicator_dot_show);
-        mHideAnimator = AnimatorInflater.loadAnimator(context,
-                R.animator.lb_page_indicator_dot_hide);
-        animators.add(mShowAnimator);
-        animators.add(mHideAnimator);
-        mAnimator.playTogether(animators);
+        mShowAnimator = new AnimatorSet();
+        mShowAnimator.playTogether(createDotAlphaAnimator(0.0f, 1.0f),
+                createDotDiameterAnimator(mDotRadius * 2, mArrowRadius * 2),
+                createDotTranslationXAnimator());
+        mHideAnimator = new AnimatorSet();
+        mHideAnimator.playTogether(createDotAlphaAnimator(1.0f, 0.0f),
+                createDotDiameterAnimator(mArrowRadius * 2, mDotRadius * 2),
+                createDotTranslationXAnimator());
+        mAnimator.playTogether(mShowAnimator, mHideAnimator);
         // Use software layer to show shadows.
         setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+    }
 
-        // To guard the methods from the proguard, the methods which is called only by the
-        // reflection should be called explicitly just once.
-        // Without this calls, the animation will not work.
-        Dot dot = new Dot();
-        dot.setTranslationX(0.0f);
-        dot.setAlpha(0.0f);
-        dot.setDiameter(0.0f);
-        dot.getTranslationX();
-        dot.getAlpha();
-        dot.getDiameter();
+    private int getDimensionFromTypedArray(TypedArray typedArray, int attr, int defaultId) {
+        return typedArray.getDimensionPixelOffset(attr,
+                getResources().getDimensionPixelOffset(defaultId));
+    }
+
+    private int getColorFromTypedArray(TypedArray typedArray, int attr, int defaultId) {
+        return typedArray.getColor(attr, getResources().getColor(defaultId));
     }
 
     private Bitmap loadArrow() {
@@ -151,6 +197,29 @@
         }
     }
 
+    private Animator createDotAlphaAnimator(float from, float to) {
+        ObjectAnimator animator = ObjectAnimator.ofFloat(null, DOT_ALPHA, from, to);
+        animator.setDuration(DURATION_ALPHA);
+        animator.setInterpolator(DECELERATE_INTERPOLATOR);
+        return animator;
+    }
+
+    private Animator createDotDiameterAnimator(float from, float to) {
+        ObjectAnimator animator = ObjectAnimator.ofFloat(null, DOT_DIAMETER, from, to);
+        animator.setDuration(DURATION_DIAMETER);
+        animator.setInterpolator(DECELERATE_INTERPOLATOR);
+        return animator;
+    }
+
+    private Animator createDotTranslationXAnimator() {
+        // The direction is determined in the Dot.
+        ObjectAnimator animator = ObjectAnimator.ofFloat(null, DOT_TRANSLATION_X,
+                -mArrowGap + mDotGap, 0.0f);
+        animator.setDuration(DURATION_TRANSLATION_X);
+        animator.setInterpolator(DECELERATE_INTERPOLATOR);
+        return animator;
+    }
+
     /**
      * Sets the page count.
      */
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
index 1b57fc5..d53e38c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -586,6 +586,9 @@
 
     /**
      * Binds the given row object to the given ViewHolder.
+     * Derived classes of {@link RowPresenter} overriding
+     * {@link #onBindRowViewHolder(ViewHolder, Object)} must call through the super class's
+     * implementation of this method.
      */
     protected void onBindRowViewHolder(ViewHolder vh, Object item) {
         vh.mRowObject = item;
@@ -602,6 +605,8 @@
 
     /**
      * Unbinds the given ViewHolder.
+     * Derived classes of {@link RowPresenter} overriding {@link #onUnbindRowViewHolder(ViewHolder)}
+     * must call through the super class's implementation of this method.
      */
     protected void onUnbindRowViewHolder(ViewHolder vh) {
         if (vh.mHeaderViewHolder != null) {
diff --git a/v17/preference-leanback/Android.mk b/v17/preference-leanback/Android.mk
index c14c4a6..bd36e32 100644
--- a/v17/preference-leanback/Android.mk
+++ b/v17/preference-leanback/Android.mk
@@ -14,25 +14,27 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# Android libraries referenced by this module's resources.
+resource_libs := \
+    android-support-v17-leanback \
+    android-support-v14-preference \
+    android-support-v7-preference \
+    android-support-v7-appcompat \
+    android-support-v7-recyclerview
+
 # Build the resources using the latest applicable SDK version.
 # We do this here because the final static library must be compiled with an older
 # SDK version than the resources.  The resources library and the R class that it
 # contains will not be linked into the final static library.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v17-preference-leanback-res
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := \
-        frameworks/support/v7/appcompat/res \
-        frameworks/support/v7/recyclerview/res \
-        frameworks/support/v7/preference/res \
-        frameworks/support/v14/preference/res \
-        frameworks/support/v17/leanback/res \
-        $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
-        --auto-add-overlay
-LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := $(resource_libs)
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_JAR_EXCLUDE_FILES := none
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files := $(LOCAL_SRC_FILES)
@@ -44,33 +46,45 @@
 LOCAL_MODULE := android-support-v17-preference-leanback-api21
 LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
-LOCAL_JAVA_LIBRARIES := android-support-v17-preference-leanback-res \
-        android-support-v17-leanback
+LOCAL_JAVA_LIBRARIES := \
+    android-support-v17-preference-leanback-res \
+    android-support-v17-leanback
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v17-preference-leanback \
+#       android-support-v17-leanback \
+#       android-support-v14-preference \
+#       android-support-v7-preference \
+#       android-support-v7-appcompat \
+#       android-support-v7-recyclerview \
+#       android-support-v4 \
+#       android-support-annotions
+#
 # in their makefiles to include the resources in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v17-preference-leanback
 LOCAL_SDK_VERSION := 17
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_STATIC_JAVA_LIBRARIES := \
-        android-support-v17-preference-leanback-api21
-LOCAL_JAVA_LIBRARIES := \
-        android-support-v4 \
-        android-support-v7-appcompat \
-        android-support-v7-recyclerview \
-        android-support-v7-preference \
-        android-support-v14-preference \
-        android-support-v17-leanback \
-        android-support-annotations \
-        android-support-v17-preference-leanback-res
+    android-support-v17-preference-leanback-api21
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    android-support-v17-preference-leanback-res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    $(resource_libs) \
+    android-support-v4 \
+    android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java b/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
index b1f9d52..27c5ad7 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
@@ -57,6 +57,7 @@
     public static final String EXTRA_SECONDARY_SIZE_ZERO = "secondarySizeZero";
     public static final String EXTRA_UPDATE_SIZE = "updateSize";
     public static final String EXTRA_LAYOUT_MARGINS = "layoutMargins";
+    public static final String EXTRA_NINEPATCH_SHADOW = "NINEPATCH_SHADOW";
 
     /**
      * Class that implements GridWidgetTest.ViewTypeProvider for creating different
@@ -102,6 +103,7 @@
     int[] mItemLengths;
     boolean[] mItemFocusables;
     int[] mLayoutMargins;
+    int mNinePatchShadow;
 
     private int mBoundCount;
 
@@ -119,6 +121,9 @@
                 if (DEBUG) Log.d(TAG, "onChildSelected position=" + position +  " id="+id);
             }
         });
+        if (mNinePatchShadow != 0) {
+            mGridView.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+        }
         return view;
     }
 
@@ -142,6 +147,7 @@
         String alignmentViewTypeClass =
                 intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS);
         String viewTypeClass = intent.getStringExtra(EXTRA_VIEWTYPEPROVIDER_CLASS);
+        mNinePatchShadow = intent.getIntExtra(EXTRA_NINEPATCH_SHADOW, 0);
         try {
             if (alignmentClass != null) {
                 mAlignmentProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
@@ -385,6 +391,13 @@
                 lp.bottomMargin = mLayoutMargins[3];
                 itemView.setLayoutParams(lp);
             }
+            if (mNinePatchShadow != 0) {
+                ViewGroup viewGroup = (ViewGroup) itemView;
+                View shadow = new View(viewGroup.getContext());
+                shadow.setBackgroundResource(mNinePatchShadow);
+                viewGroup.addView(shadow);
+                viewGroup.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+            }
             return new ViewHolder(itemView);
         }
 
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
index 8302cfd..91a114f 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -484,6 +484,76 @@
 
     }
 
+    public void testItemDecorationAndMarginsAndOpticalBounds() throws Throwable {
+        final int leftMargin = 3;
+        final int topMargin = 4;
+        final int rightMargin = 7;
+        final int bottomMargin = 8;
+        final int itemHeight = 100;
+        final int ninePatchDrawableResourceId = R.drawable.lb_card_shadow_focused;
+
+        mInstrumentation = getInstrumentation();
+        Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
+                new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
+        intent.putExtra(GridActivity.EXTRA_NINEPATCH_SHADOW, ninePatchDrawableResourceId);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        final int paddingLeft = mGridView.getPaddingLeft();
+        final int paddingTop = mGridView.getPaddingTop();
+        final int verticalSpace = mGridView.getVerticalMargin();
+        final int decorationLeft = 17;
+        final int decorationTop = 1;
+        final int decorationRight = 19;
+        final int decorationBottom = 2;
+
+        final Rect opticalPaddings = new Rect();
+        mGridView.getContext().getDrawable(ninePatchDrawableResourceId).getPadding(opticalPaddings);
+        final int opticalInsetsLeft = opticalPaddings.left;
+        final int opticalInsetsTop = opticalPaddings.top;
+        final int opticalInsetsRight = opticalPaddings.right;
+        final int opticalInsetsBottom = opticalPaddings.bottom;
+        assertTrue(opticalInsetsLeft > 0);
+        assertTrue(opticalInsetsTop > 0);
+        assertTrue(opticalInsetsRight > 0);
+        assertTrue(opticalInsetsBottom > 0);
+
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
+                        decorationRight, decorationBottom));
+            }
+        });
+        waitForScrollIdle();
+
+        View child0 = mGridView.getChildAt(0);
+        View child1 = mGridView.getChildAt(1);
+        View child2 = mGridView.getChildAt(2);
+
+        assertEquals(itemHeight + opticalInsetsTop + opticalInsetsBottom,
+                child0.getBottom() - child0.getTop());
+
+        // verify left margins decoration and optical insets
+        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+                child0.getLeft());
+        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+                child1.getLeft());
+        assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+                child2.getLeft());
+        // verify top bottom margins decoration offset and optical insets
+        assertEquals(paddingTop + topMargin + decorationTop, child0.getTop() + opticalInsetsTop);
+        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+                (child1.getTop() + opticalInsetsTop) - (child0.getBottom() - opticalInsetsBottom));
+        assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+                (child2.getTop() + opticalInsetsTop) - (child1.getBottom() - opticalInsetsBottom));
+
+    }
+
     public void testThreeColumnVerticalBasic() throws Throwable {
 
         mInstrumentation = getInstrumentation();
diff --git a/v4/Android.mk b/v4/Android.mk
index dd24664..1e8adad 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -264,12 +264,15 @@
 
 # Here is the final static library that apps can link against.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v4
 LOCAL_SDK_VERSION := 4
 LOCAL_AIDL_INCLUDES := frameworks/support/v4/java
 LOCAL_SRC_FILES := $(call all-java-files-under, java) \
     $(call all-Iaidl-files-under, java)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api24
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/v4/api/current.txt b/v4/api/current.txt
index 2cf8a11..08c19ee 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -89,6 +89,12 @@
     method public void update(android.support.v4.app.ActivityOptionsCompat);
   }
 
+  public class AppLaunchChecker {
+    ctor public AppLaunchChecker();
+    method public static boolean hasStartedFromLauncher(android.content.Context);
+    method public static void onActivityCreate(android.app.Activity);
+  }
+
   public final class AppOpsManagerCompat {
     method public static int noteOp(android.content.Context, java.lang.String, int, java.lang.String);
     method public static int noteProxyOp(android.content.Context, java.lang.String, java.lang.String);
@@ -98,6 +104,9 @@
     field public static final int MODE_IGNORED = 1; // 0x1
   }
 
+   abstract class BaseFragmentActivityApi24 extends android.support.v4.app.BaseFragmentActivityHoneycomb {
+  }
+
    abstract class BaseFragmentActivityDonut extends android.app.Activity {
   }
 
@@ -191,9 +200,11 @@
     method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
     method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
     method public void onLowMemory();
+    method public void onMultiWindowModeChanged(boolean);
     method public boolean onOptionsItemSelected(android.view.MenuItem);
     method public void onOptionsMenuClosed(android.view.Menu);
     method public void onPause();
+    method public void onPictureInPictureModeChanged(boolean);
     method public void onPrepareOptionsMenu(android.view.Menu);
     method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
     method public void onResume();
@@ -239,7 +250,7 @@
     field public static final android.os.Parcelable.Creator<android.support.v4.app.Fragment.SavedState> CREATOR;
   }
 
-  public class FragmentActivity extends android.support.v4.app.BaseFragmentActivityHoneycomb implements android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback {
+  public class FragmentActivity extends android.support.v4.app.BaseFragmentActivityApi24 implements android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback {
     ctor public FragmentActivity();
     method public java.lang.Object getLastCustomNonConfigurationInstance();
     method public android.support.v4.app.FragmentManager getSupportFragmentManager();
@@ -278,9 +289,11 @@
     method public void dispatchDestroy();
     method public void dispatchDestroyView();
     method public void dispatchLowMemory();
+    method public void dispatchMultiWindowModeChanged(boolean);
     method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
     method public void dispatchOptionsMenuClosed(android.view.Menu);
     method public void dispatchPause();
+    method public void dispatchPictureInPictureModeChanged(boolean);
     method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
     method public void dispatchReallyStop();
     method public void dispatchResume();
@@ -574,11 +587,13 @@
     method public android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Builder);
     method public java.lang.CharSequence getCancelLabel();
     method public java.lang.CharSequence getConfirmLabel();
+    method public boolean getHintLaunchesActivity();
     method public java.lang.CharSequence getInProgressLabel();
     method public boolean isAvailableOffline();
     method public android.support.v4.app.NotificationCompat.Action.WearableExtender setAvailableOffline(boolean);
     method public android.support.v4.app.NotificationCompat.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
     method public android.support.v4.app.NotificationCompat.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+    method public android.support.v4.app.NotificationCompat.Action.WearableExtender setHintLaunchesActivity(boolean);
     method public android.support.v4.app.NotificationCompat.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
   }
 
@@ -721,6 +736,7 @@
     method public android.app.PendingIntent getDisplayIntent();
     method public int getGravity();
     method public boolean getHintAvoidBackgroundClipping();
+    method public boolean getHintContentIntentLaunchesActivity();
     method public boolean getHintHideIcon();
     method public int getHintScreenTimeout();
     method public boolean getHintShowBackgroundOnly();
@@ -736,6 +752,7 @@
     method public android.support.v4.app.NotificationCompat.WearableExtender setDisplayIntent(android.app.PendingIntent);
     method public android.support.v4.app.NotificationCompat.WearableExtender setGravity(int);
     method public android.support.v4.app.NotificationCompat.WearableExtender setHintAvoidBackgroundClipping(boolean);
+    method public android.support.v4.app.NotificationCompat.WearableExtender setHintContentIntentLaunchesActivity(boolean);
     method public android.support.v4.app.NotificationCompat.WearableExtender setHintHideIcon(boolean);
     method public android.support.v4.app.NotificationCompat.WearableExtender setHintScreenTimeout(int);
     method public android.support.v4.app.NotificationCompat.WearableExtender setHintShowBackgroundOnly(boolean);
@@ -769,6 +786,7 @@
   }
 
   public final class NotificationManagerCompat {
+    method public boolean areNotificationsEnabled();
     method public void cancel(int);
     method public void cancel(java.lang.String, int);
     method public void cancelAll();
@@ -780,6 +798,16 @@
     field public static final java.lang.String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
   }
 
+  public class NotificationManagerCompatApi24 {
+    ctor public NotificationManagerCompatApi24();
+    method public static boolean areNotificationsEnabled(android.app.NotificationManager);
+  }
+
+  public class NotificationManagerCompatKitKat {
+    ctor public NotificationManagerCompatKitKat();
+    method public static boolean areNotificationsEnabled(android.content.Context);
+  }
+
   public final class RemoteInput extends android.support.v4.app.RemoteInputCompatBase.RemoteInput {
     method public static void addResultsToIntent(android.support.v4.app.RemoteInput[], android.content.Intent, android.os.Bundle);
     method public boolean getAllowFreeFormInput();
@@ -977,6 +1005,7 @@
     method public static android.content.Intent makeRestartActivityTask(android.content.ComponentName);
     field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
     field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
+    field public static final java.lang.String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
     field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
     field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
     field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
@@ -2509,6 +2538,7 @@
     method public static int getLabelFor(android.view.View);
     method public static int getLayerType(android.view.View);
     method public static int getLayoutDirection(android.view.View);
+    method public static android.graphics.Matrix getMatrix(android.view.View);
     method public static int getMeasuredHeightAndState(android.view.View);
     method public static int getMeasuredState(android.view.View);
     method public static int getMeasuredWidthAndState(android.view.View);
@@ -3252,7 +3282,9 @@
     ctor public DrawerLayout(android.content.Context, android.util.AttributeSet, int);
     method public void addDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
     method public void closeDrawer(android.view.View);
+    method public void closeDrawer(android.view.View, boolean);
     method public void closeDrawer(int);
+    method public void closeDrawer(int, boolean);
     method public void closeDrawers();
     method public float getDrawerElevation();
     method public int getDrawerLockMode(int);
@@ -3266,7 +3298,9 @@
     method public void onDraw(android.graphics.Canvas);
     method protected void onLayout(boolean, int, int, int, int);
     method public void openDrawer(android.view.View);
+    method public void openDrawer(android.view.View, boolean);
     method public void openDrawer(int);
+    method public void openDrawer(int, boolean);
     method public void removeDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
     method public void setDrawerElevation(float);
     method public deprecated void setDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
diff --git a/v4/api21/android/support/v4/view/ViewCompatLollipop.java b/v4/api21/android/support/v4/view/ViewCompatLollipop.java
index fddbccb..167ae17 100644
--- a/v4/api21/android/support/v4/view/ViewCompatLollipop.java
+++ b/v4/api21/android/support/v4/view/ViewCompatLollipop.java
@@ -18,13 +18,17 @@
 
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.view.View;
+import android.view.ViewParent;
 import android.view.WindowInsets;
 
 class ViewCompatLollipop {
 
+    private static ThreadLocal<Rect> sThreadLocalRect;
+
     public static void setTransitionName(View view, String transitionName) {
         view.setTransitionName(transitionName);
     }
@@ -193,4 +197,67 @@
     public static float getZ(View view) {
         return view.getZ();
     }
+
+    static void offsetTopAndBottom(final View view, final int offset) {
+        final Rect parentRect = getEmptyTempRect();
+        boolean needInvalidateWorkaround = false;
+
+        final ViewParent parent = view.getParent();
+        if (parent instanceof View) {
+            final View p = (View) parent;
+            parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
+            // If the view currently does not currently intersect the parent (and is therefore
+            // not displayed) we may need need to invalidate
+            needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
+                    view.getRight(), view.getBottom());
+        }
+
+        // Now offset, invoking the API 11+ implementation (which contains it's own workarounds)
+        ViewCompatHC.offsetTopAndBottom(view, offset);
+
+        // The view has now been offset, so let's intersect the Rect and invalidate where
+        // the View is now displayed
+        if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
+                view.getRight(), view.getBottom())) {
+            ((View) parent).invalidate(parentRect);
+        }
+    }
+
+    static void offsetLeftAndRight(final View view, final int offset) {
+        final Rect parentRect = getEmptyTempRect();
+        boolean needInvalidateWorkaround = false;
+
+        final ViewParent parent = view.getParent();
+        if (parent instanceof View) {
+            final View p = (View) parent;
+            parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
+            // If the view currently does not currently intersect the parent (and is therefore
+            // not displayed) we may need need to invalidate
+            needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
+                    view.getRight(), view.getBottom());
+        }
+
+        // Now offset, invoking the API 11+ implementation (which contains it's own workarounds)
+        ViewCompatHC.offsetLeftAndRight(view, offset);
+
+        // The view has now been offset, so let's intersect the Rect and invalidate where
+        // the View is now displayed
+        if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
+                view.getRight(), view.getBottom())) {
+            ((View) parent).invalidate(parentRect);
+        }
+    }
+
+    private static Rect getEmptyTempRect() {
+        if (sThreadLocalRect == null) {
+            sThreadLocalRect = new ThreadLocal<>();
+        }
+        Rect rect = sThreadLocalRect.get();
+        if (rect == null) {
+            rect = new Rect();
+            sThreadLocalRect.set(rect);
+        }
+        rect.setEmpty();
+        return rect;
+    }
 }
diff --git a/v4/api23/android/support/v4/print/PrintHelperApi23.java b/v4/api23/android/support/v4/print/PrintHelperApi23.java
index 5129cd3..ba646e3 100644
--- a/v4/api23/android/support/v4/print/PrintHelperApi23.java
+++ b/v4/api23/android/support/v4/print/PrintHelperApi23.java
@@ -25,7 +25,13 @@
 class PrintHelperApi23 extends PrintHelperApi20 {
     @Override
     protected PrintAttributes.Builder copyAttributes(PrintAttributes other) {
-        return super.copyAttributes(other).setDuplexMode(other.getDuplexMode());
+        PrintAttributes.Builder b = super.copyAttributes(other);
+
+        if (other.getDuplexMode() != 0) {
+            b.setDuplexMode(other.getDuplexMode());
+        }
+
+        return b;
     }
 
     PrintHelperApi23(Context context) {
diff --git a/v4/api24/android/support/v4/app/BaseFragmentActivityApi24.java b/v4/api24/android/support/v4/app/BaseFragmentActivityApi24.java
new file mode 100644
index 0000000..7756648
--- /dev/null
+++ b/v4/api24/android/support/v4/app/BaseFragmentActivityApi24.java
@@ -0,0 +1,49 @@
+/*
+ * 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.v4.app;
+
+import android.support.annotation.CallSuper;
+import android.support.v4.os.BuildCompat;
+
+/**
+ * Base class for {@code FragmentActivity} to be able to use v24 APIs.
+ */
+abstract class BaseFragmentActivityApi24 extends BaseFragmentActivityHoneycomb {
+
+    @Override
+    @CallSuper
+    public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+        if (BuildCompat.isAtLeastN()) {
+            super.onMultiWindowModeChanged(isInMultiWindowMode);
+        }
+        dispatchFragmentsOnMultiWindowModeChanged(isInMultiWindowMode);
+    }
+
+    @Override
+    @CallSuper
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        if (BuildCompat.isAtLeastN()) {
+            super.onPictureInPictureModeChanged(isInPictureInPictureMode);
+        }
+        dispatchFragmentsOnPictureInPictureModeChanged(isInPictureInPictureMode);
+    }
+
+    abstract void dispatchFragmentsOnMultiWindowModeChanged(boolean isInMultiWindowMode);
+
+    abstract void dispatchFragmentsOnPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+
+}
diff --git a/v4/api24/android/support/v4/app/NotificationManagerCompatApi24.java b/v4/api24/android/support/v4/app/NotificationManagerCompatApi24.java
new file mode 100644
index 0000000..bbd3bde
--- /dev/null
+++ b/v4/api24/android/support/v4/app/NotificationManagerCompatApi24.java
@@ -0,0 +1,25 @@
+/*
+ * 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.v4.app;
+
+import android.app.NotificationManager;
+import android.content.Context;
+
+public class NotificationManagerCompatApi24 {
+    public static boolean areNotificationsEnabled(NotificationManager notificationManager) {
+        return notificationManager.areNotificationsEnabled();
+    }
+}
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
index 98ea53f..60bb2c4 100644
--- a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
@@ -57,16 +57,7 @@
      * @param dr the drawable to wrap
      */
     DrawableWrapperDonut(@Nullable Drawable dr) {
-        // The following is workaround for issues for certain DrawableContainers on some API levels.
-        // They expect getConstantState() to always return non-null, which will only happen after
-        // we have been mutated. Since most Drawables provided to us will be from Resources,
-        // they will nearly always have been mutated, so we should act as if we have been too.
-        // This means that we should copy our input's CS to our own state. This satisfies the
-        // canConstantState() check below. If the input does not provide a CS, then there's nothing
-        // we can do anyway.
-        if (dr != null && dr.getConstantState() != null) {
-            mState = mutateConstantState();
-        }
+        mState = mutateConstantState();
         // Now set the drawable...
         setWrappedDrawable(dr);
     }
@@ -137,7 +128,9 @@
 
     @Override
     public boolean isStateful() {
-        final ColorStateList tintList = isCompatTintEnabled() ? mState.mTint : null;
+        final ColorStateList tintList = (isCompatTintEnabled() && mState != null)
+                ? mState.mTint
+                : null;
         return (tintList != null && tintList.isStateful()) || mDrawable.isStateful();
     }
 
diff --git a/v4/java/android/support/v4/os/BuildCompat.java b/v4/donut/android/support/v4/os/BuildCompat.java
similarity index 100%
rename from v4/java/android/support/v4/os/BuildCompat.java
rename to v4/donut/android/support/v4/os/BuildCompat.java
diff --git a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
index 4804fb6..5067cf7 100644
--- a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
+++ b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
@@ -17,6 +17,7 @@
 package android.support.v4.view;
 
 import android.animation.ValueAnimator;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.view.View;
 import android.view.ViewParent;
@@ -98,6 +99,10 @@
         view.setTranslationY(value);
     }
 
+    public static Matrix getMatrix(View view) {
+        return view.getMatrix();
+    }
+
     public static void setAlpha(View view, float value) {
         view.setAlpha(value);
     }
diff --git a/v4/java/android/support/v4/app/AppLaunchChecker.java b/v4/java/android/support/v4/app/AppLaunchChecker.java
new file mode 100644
index 0000000..86219d4
--- /dev/null
+++ b/v4/java/android/support/v4/app/AppLaunchChecker.java
@@ -0,0 +1,84 @@
+/*
+ * 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.v4.app;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.v4.content.IntentCompat;
+import android.support.v4.content.SharedPreferencesCompat;
+
+/**
+ * This class provides APIs for determining how an app has been launched.
+ * This can be useful if you want to confirm that a user has launched your
+ * app through its front door activity from their launcher/home screen, rather
+ * than just if the app has been opened in the past in order to view a link,
+ * open a document or perform some other service for other apps on the device.
+ */
+public class AppLaunchChecker {
+    private static final String SHARED_PREFS_NAME = "android.support.AppLaunchChecker";
+    private static final String KEY_STARTED_FROM_LAUNCHER = "startedFromLauncher";
+
+    /**
+     * Checks if this app has been launched by the user from their launcher or home screen
+     * since it was installed.
+     *
+     * <p>To track this state properly you must call {@link #onActivityCreate(Activity)}
+     * in your launcher activity's {@link Activity#onCreate(Bundle)} method.</p>
+     *
+     * @param context Context to check
+     * @return true if this app has been started by the user from the launcher at least once
+     */
+    public static boolean hasStartedFromLauncher(Context context) {
+        return context.getSharedPreferences(SHARED_PREFS_NAME, 0)
+                .getBoolean(KEY_STARTED_FROM_LAUNCHER, false);
+    }
+
+    /**
+     * Records the parameters of an activity's launch for later use by the other
+     * methods available on this class.
+     *
+     * <p>Your app should call this method in your launcher activity's
+     * {@link Activity#onCreate(Bundle)} method to track launch state.
+     * If the app targets API 23 (Android 6.0 Marshmallow) or later, this state will be
+     * eligible for full data backup and may be restored to the user's device automatically.</p>     *
+     *
+     * @param activity the Activity currently running onCreate
+     */
+    public static void onActivityCreate(Activity activity) {
+        final SharedPreferences sp = activity.getSharedPreferences(SHARED_PREFS_NAME, 0);
+        if (sp.getBoolean(KEY_STARTED_FROM_LAUNCHER, false)) {
+            return;
+        }
+
+        final Intent launchIntent = activity.getIntent();
+        if (launchIntent == null) {
+            return;
+        }
+
+        if (Intent.ACTION_MAIN.equals(launchIntent.getAction())
+                && (launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
+                || launchIntent.hasCategory(IntentCompat.CATEGORY_LEANBACK_LAUNCHER))) {
+            SharedPreferencesCompat.EditorCompat.getInstance().apply(
+                    sp.edit().putBoolean(KEY_STARTED_FROM_LAUNCHER, true));
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/app/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index bec151c..ee0d758 100644
--- a/v4/java/android/support/v4/app/BackStackRecord.java
+++ b/v4/java/android/support/v4/app/BackStackRecord.java
@@ -134,6 +134,10 @@
                     op.removed.add(r);
                 }
             }
+            bse.mEnterAnim = op.enterAnim;
+            bse.mExitAnim = op.exitAnim;
+            bse.mPopEnterAnim = op.popEnterAnim;
+            bse.mPopExitAnim = op.popExitAnim;
             bse.addOp(op);
             num++;
         }
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index 30f92a0..4918dfb 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -876,11 +876,12 @@
      *                        false if it is not.
      */
     public void setUserVisibleHint(boolean isVisibleToUser) {
-        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && mFragmentManager != null) {
+        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
+                && mFragmentManager != null && isAdded()) {
             mFragmentManager.performPendingDeferredStart(this);
         }
         mUserVisibleHint = isVisibleToUser;
-        mDeferStart = !isVisibleToUser;
+        mDeferStart = mState < STARTED && !isVisibleToUser;
     }
 
     /**
@@ -1038,7 +1039,7 @@
         if (mHost == null) {
             throw new IllegalStateException("Fragment " + this + " not attached to Activity");
         }
-        mHost.onRequestPermissionsFromFragment(this, permissions,requestCode);
+        mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
     }
 
     /**
@@ -1374,11 +1375,30 @@
      */
     public void onSaveInstanceState(Bundle outState) {
     }
-    
+
+    /**
+     * Called when the Fragment's activity changes from fullscreen mode to multi-window mode and
+     * visa-versa. This is generally tied to {@link Activity#onMultiWindowModeChanged} of the
+     * containing Activity.
+     *
+     * @param isInMultiWindowMode True if the activity is in multi-window mode.
+     */
+    public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+    }
+
+    /**
+     * Called by the system when the activity changes to and from picture-in-picture mode. This is
+     * generally tied to {@link Activity#onPictureInPictureModeChanged} of the containing Activity.
+     *
+     * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+     */
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+    }
+
     public void onConfigurationChanged(Configuration newConfig) {
         mCalled = true;
     }
-    
+
     /**
      * Called when the Fragment is no longer resumed.  This is generally
      * tied to {@link Activity#onPause() Activity.onPause} of the containing
@@ -2070,6 +2090,20 @@
         }
     }
 
+    void performMultiWindowModeChanged(boolean isInMultiWindowMode) {
+        onMultiWindowModeChanged(isInMultiWindowMode);
+        if (mChildFragmentManager != null) {
+            mChildFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+        }
+    }
+
+    void performPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        onPictureInPictureModeChanged(isInPictureInPictureMode);
+        if (mChildFragmentManager != null) {
+            mChildFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+        }
+    }
+
     void performConfigurationChanged(Configuration newConfig) {
         onConfigurationChanged(newConfig);
         if (mChildFragmentManager != null) {
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index fd35a7a..ea61fff 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -42,8 +42,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Base class for activities that want to use the support-based
@@ -77,7 +75,7 @@
  * state, this may be a snapshot slightly before what the user last saw.</p>
  * </ul>
  */
-public class FragmentActivity extends BaseFragmentActivityHoneycomb implements
+public class FragmentActivity extends BaseFragmentActivityApi24 implements
         ActivityCompat.OnRequestPermissionsResultCallback,
         ActivityCompatApi23.RequestPermissionsRequestCodeValidator {
     private static final String TAG = "FragmentActivity";
@@ -279,6 +277,26 @@
     }
 
     /**
+     * Dispatch multi-window mode change to all fragments.
+     *
+     * @see Activity#onMultiWindowModeChanged(boolean)
+     */
+    @Override
+    void dispatchFragmentsOnMultiWindowModeChanged(boolean isInMultiWindowMode) {
+        mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+    }
+
+    /**
+     * Dispatch picture-in-picture mode change to all fragments.
+     *
+     * @see Activity#onPictureInPictureModeChanged(boolean)
+     */
+    @Override
+    void dispatchFragmentsOnPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        mFragments.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+    }
+
+    /**
      * Dispatch configuration change to all fragments.
      */
     @Override
diff --git a/v4/java/android/support/v4/app/FragmentController.java b/v4/java/android/support/v4/app/FragmentController.java
index 60beed9..d413a42 100644
--- a/v4/java/android/support/v4/app/FragmentController.java
+++ b/v4/java/android/support/v4/app/FragmentController.java
@@ -270,6 +270,28 @@
     }
 
     /**
+     * Lets all Fragments managed by the controller's FragmentManager know the multi-window mode of
+     * the activity changed.
+     * <p>Call when the multi-window mode of the activity changed.
+     *
+     * @see Fragment#onMultiWindowModeChanged
+     */
+    public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
+        mHost.mFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+    }
+
+    /**
+     * Lets all Fragments managed by the controller's FragmentManager know the picture-in-picture
+     * mode of the activity changed.
+     * <p>Call when the picture-in-picture mode of the activity changed.
+     *
+     * @see Fragment#onPictureInPictureModeChanged
+     */
+    public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        mHost.mFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+    }
+
+    /**
      * Lets all Fragments managed by the controller's FragmentManager
      * know a configuration change occurred.
      * <p>Call when there is a configuration change.
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index ca7e484..11cf28d 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -2172,7 +2172,31 @@
         mContainer = null;
         mParent = null;
     }
-    
+
+    public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
+        if (mAdded == null) {
+            return;
+        }
+        for (int i = mAdded.size() - 1; i >= 0; --i) {
+            final android.support.v4.app.Fragment f = mAdded.get(i);
+            if (f != null) {
+                f.performMultiWindowModeChanged(isInMultiWindowMode);
+            }
+        }
+    }
+
+    public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        if (mAdded == null) {
+            return;
+        }
+        for (int i = mAdded.size() - 1; i >= 0; --i) {
+            final android.support.v4.app.Fragment f = mAdded.get(i);
+            if (f != null) {
+                f.performPictureInPictureModeChanged(isInPictureInPictureMode);
+            }
+        }
+    }
+
     public void dispatchConfigurationChanged(Configuration newConfig) {
         if (mAdded != null) {
             for (int i=0; i<mAdded.size(); i++) {
diff --git a/v4/java/android/support/v4/app/LoaderManager.java b/v4/java/android/support/v4/app/LoaderManager.java
index fad56e0..9ec2cd5 100644
--- a/v4/java/android/support/v4/app/LoaderManager.java
+++ b/v4/java/android/support/v4/app/LoaderManager.java
@@ -311,7 +311,7 @@
             if (mStarted) {
                 if (mReportNextStart) {
                     mReportNextStart = false;
-                    if (mHaveData) {
+                    if (mHaveData && !mRetaining) {
                         callOnLoadFinished(mLoader, mData);
                     }
                 }
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index e6cbd79..9a9bdb6 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -16,6 +16,7 @@
 
 package android.support.v4.app;
 
+import android.app.Activity;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -2001,6 +2002,7 @@
 
             // Flags bitwise-ored to mFlags
             private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
+            private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
 
             // Default value for flags integer
             private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
@@ -2163,6 +2165,30 @@
             public CharSequence getCancelLabel() {
                 return mCancelLabel;
             }
+
+            /**
+             * Set a hint that this Action will launch an {@link Activity} directly, telling the
+             * platform that it can generate the appropriate transitions.
+             * @param hintLaunchesActivity {@code true} if the content intent will launch
+             * an activity and transitions should be generated, false otherwise.
+             * @return this object for method chaining
+             */
+            public WearableExtender setHintLaunchesActivity(
+                    boolean hintLaunchesActivity) {
+                setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
+                return this;
+            }
+
+            /**
+             * Get a hint that this Action will launch an {@link Activity} directly, telling the
+             * platform that it can generate the appropriate transitions
+             * @return {@code true} if the content intent will launch an activity and transitions
+             * should be generated, false otherwise. The default value is {@code false} if this was
+             * never set.
+             */
+            public boolean getHintLaunchesActivity() {
+                return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
+            }
         }
 
         /** @hide */
@@ -2326,6 +2352,7 @@
         private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
         private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
         private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
+        private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
 
         // Default value for flags integer
         private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
@@ -2897,6 +2924,29 @@
             return mHintScreenTimeout;
         }
 
+        /**
+         * Set a hint that this notification's content intent will launch an {@link Activity}
+         * directly, telling the platform that it can generate the appropriate transitions.
+         * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
+         * an activity and transitions should be generated, false otherwise.
+         * @return this object for method chaining
+         */
+        public WearableExtender setHintContentIntentLaunchesActivity(
+                boolean hintContentIntentLaunchesActivity) {
+            setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
+            return this;
+        }
+
+        /**
+         * Get a hint that this notification's content intent will launch an {@link Activity}
+         * directly, telling the platform that it can generate the appropriate transitions
+         * @return {@code true} if the content intent will launch an activity and transitions should
+         * be generated, false otherwise. The default value is {@code false} if this was never set.
+         */
+        public boolean getHintContentIntentLaunchesActivity() {
+            return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
+        }
+
         private void setFlag(int mask, boolean value) {
             if (value) {
                 mFlags |= mask;
diff --git a/v4/java/android/support/v4/app/NotificationManagerCompat.java b/v4/java/android/support/v4/app/NotificationManagerCompat.java
index e2b6fc9..b87973f 100644
--- a/v4/java/android/support/v4/app/NotificationManagerCompat.java
+++ b/v4/java/android/support/v4/app/NotificationManagerCompat.java
@@ -34,6 +34,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.provider.Settings;
+import android.support.v4.os.BuildCompat;
 import android.util.Log;
 
 import java.util.HashMap;
@@ -118,6 +119,8 @@
                 Notification notification);
 
         int getSideChannelBindFlags();
+
+        boolean areNotificationsEnabled(Context context, NotificationManager notificationManager);
     }
 
     static class ImplBase implements Impl {
@@ -137,6 +140,12 @@
         public int getSideChannelBindFlags() {
             return Service.BIND_AUTO_CREATE;
         }
+
+        @Override
+        public boolean areNotificationsEnabled(Context context,
+                NotificationManager notificationManager) {
+            return true;
+        }
     }
 
     static class ImplEclair extends ImplBase {
@@ -161,8 +170,28 @@
         }
     }
 
+    static class ImplKitKat extends ImplIceCreamSandwich {
+        @Override
+        public boolean areNotificationsEnabled(Context context,
+                NotificationManager notificationManager) {
+            return NotificationManagerCompatKitKat.areNotificationsEnabled(context);
+        }
+    }
+
+    static class ImplApi24 extends ImplKitKat {
+        @Override
+        public boolean areNotificationsEnabled(Context context,
+                NotificationManager notificationManager) {
+            return NotificationManagerCompatApi24.areNotificationsEnabled(notificationManager);
+        }
+    }
+
     static {
-        if (Build.VERSION.SDK_INT >= 14) {
+        if (BuildCompat.isAtLeastN()) {
+            IMPL = new ImplApi24();
+        } else if (Build.VERSION.SDK_INT >= 19) {
+            IMPL = new ImplKitKat();
+        }  else if (Build.VERSION.SDK_INT >= 14) {
             IMPL = new ImplIceCreamSandwich();
         } else if (Build.VERSION.SDK_INT >= 5) {
             IMPL = new ImplEclair();
@@ -227,6 +256,13 @@
     }
 
     /**
+     * Returns whether notifications from the calling package are not blocked.
+     */
+    public boolean areNotificationsEnabled() {
+        return IMPL.areNotificationsEnabled(mContext, mNotificationManager);
+    }
+
+    /**
      * Get the set of packages that have an enabled notification listener component within them.
      */
     public static Set<String> getEnabledListenerPackages(Context context) {
diff --git a/v4/java/android/support/v4/content/ContextCompat.java b/v4/java/android/support/v4/content/ContextCompat.java
index efb1e38..ddafb00 100644
--- a/v4/java/android/support/v4/content/ContextCompat.java
+++ b/v4/java/android/support/v4/content/ContextCompat.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
@@ -29,6 +30,7 @@
 import android.support.v4.os.BuildCompat;
 import android.support.v4.os.EnvironmentCompat;
 import android.util.Log;
+import android.util.TypedValue;
 
 import java.io.File;
 
@@ -45,6 +47,10 @@
     private static final String DIR_FILES = "files";
     private static final String DIR_CACHE = "cache";
 
+    private static final Object sLock = new Object();
+
+    private static TypedValue sTempValue;
+
     /**
      * Start a set of activities as a synthesized task stack, if able.
      *
@@ -330,22 +336,36 @@
     }
 
     /**
-     * Return a drawable object associated with a particular resource ID.
+     * Returns a drawable object associated with a particular resource ID.
      * <p>
-     * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the returned
-     * drawable will be styled for the specified Context's theme.
+     * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the
+     * returned drawable will be styled for the specified Context's theme.
      *
      * @param id The desired resource identifier, as generated by the aapt tool.
-     *            This integer encodes the package, type, and resource entry.
-     *            The value 0 is an invalid identifier.
+     *           This integer encodes the package, type, and resource entry.
+     *           The value 0 is an invalid identifier.
      * @return Drawable An object that can be used to draw this resource.
      */
     public static final Drawable getDrawable(Context context, int id) {
         final int version = Build.VERSION.SDK_INT;
         if (version >= 21) {
             return ContextCompatApi21.getDrawable(context, id);
-        } else {
+        } else if (version >= 16) {
             return context.getResources().getDrawable(id);
+        } else {
+            // Prior to JELLY_BEAN, Resources.getDrawable() would not correctly
+            // retrieve the final configuration density when the resource ID
+            // is a reference another Drawable resource. As a workaround, try
+            // to resolve the drawable reference manually.
+            final int resolvedId;
+            synchronized (sLock) {
+                if (sTempValue == null) {
+                    sTempValue = new TypedValue();
+                }
+                context.getResources().getValue(id, sTempValue, true);
+                resolvedId = sTempValue.resourceId;
+            }
+            return context.getResources().getDrawable(resolvedId);
         }
     }
 
diff --git a/v4/java/android/support/v4/content/IntentCompat.java b/v4/java/android/support/v4/content/IntentCompat.java
index 97489cd..eaf7b1f 100644
--- a/v4/java/android/support/v4/content/IntentCompat.java
+++ b/v4/java/android/support/v4/content/IntentCompat.java
@@ -174,6 +174,12 @@
     public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
 
     /**
+     * Indicates an activity optimized for Leanback mode, and that should
+     * be displayed in the Leanback launcher.
+     */
+    public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
+
+    /**
      * If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
      * this flag will cause a newly launching task to be placed on top of the current
      * home activity task (if there is one). That is, pressing back from the task
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index 02e9a6d..23ab4b7 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -103,7 +103,7 @@
         } else if (Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
         } else {
-            mImpl = new MediaBrowserServiceImplBase(context, serviceComponent, callback, rootHints);
+            mImpl = new MediaBrowserImplBase(context, serviceComponent, callback, rootHints);
         }
     }
 
@@ -723,7 +723,7 @@
         void onLoadChildren(Messenger callback, String parentId, List list, Bundle options);
     }
 
-    static class MediaBrowserServiceImplBase
+    static class MediaBrowserImplBase
             implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl {
         private static final boolean DBG = false;
 
@@ -747,7 +747,7 @@
         private MediaSessionCompat.Token mMediaSessionToken;
         private Bundle mExtras;
 
-        public MediaBrowserServiceImplBase(Context context, ComponentName serviceComponent,
+        public MediaBrowserImplBase(Context context, ComponentName serviceComponent,
                 ConnectionCallback callback, Bundle rootHints) {
             if (context == null) {
                 throw new IllegalArgumentException("context must not be null");
@@ -863,6 +863,7 @@
             mServiceConnection = null;
             mServiceBinderWrapper = null;
             mCallbacksMessenger = null;
+            mHandler.setCallbacksMessenger(null);
             mRootId = null;
             mMediaSessionToken = null;
         }
@@ -1273,6 +1274,13 @@
 
         @Override
         public void disconnect() {
+            if (mServiceBinderWrapper != null && mCallbacksMessenger != null) {
+                try {
+                    mServiceBinderWrapper.unregisterCallbackMessenger(mCallbacksMessenger);
+                } catch (RemoteException e) {
+                    Log.i(TAG, "Remote error unregistering client messenger." );
+                }
+            }
             MediaBrowserCompatApi21.disconnect(mBrowserObj);
         }
 
@@ -1431,6 +1439,7 @@
         public void onConnectionSuspended() {
             mServiceBinderWrapper = null;
             mCallbacksMessenger = null;
+            mHandler.setCallbacksMessenger(null);
         }
 
         @Override
@@ -1683,6 +1692,10 @@
             sendRequest(CLIENT_MSG_REGISTER_CALLBACK_MESSENGER, null, callbackMessenger);
         }
 
+        void unregisterCallbackMessenger(Messenger callbackMessenger) throws RemoteException {
+            sendRequest(CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER, null, callbackMessenger);
+        }
+
         private void sendRequest(int what, Bundle data, Messenger cbMessenger)
                 throws RemoteException {
             Message msg = Message.obtain();
diff --git a/v4/java/android/support/v4/media/MediaBrowserProtocol.java b/v4/java/android/support/v4/media/MediaBrowserProtocol.java
index aea3ddd..b0fee56 100644
--- a/v4/java/android/support/v4/media/MediaBrowserProtocol.java
+++ b/v4/java/android/support/v4/media/MediaBrowserProtocol.java
@@ -143,4 +143,11 @@
      * - replyTo : Callback messenger
      */
     public static final int CLIENT_MSG_REGISTER_CALLBACK_MESSENGER = 6;
+
+    /** (client v1)
+     * Sent to unregister the client messenger
+     * - arg1 : The client version
+     * - replyTo : Callback messenger
+     */
+    public static final int CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER = 7;
 }
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index 8122980..f24639a 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -196,6 +196,9 @@
                 case CLIENT_MSG_REGISTER_CALLBACK_MESSENGER:
                     mServiceImpl.registerCallbacks(new ServiceCallbacksCompat(msg.replyTo));
                     break;
+                case CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER:
+                    mServiceImpl.unregisterCallbacks(new ServiceCallbacksCompat(msg.replyTo));
+                    break;
                 default:
                     Log.w(TAG, "Unhandled message: " + msg
                             + "\n  Service version: " + SERVICE_VERSION_CURRENT
@@ -440,6 +443,17 @@
                 }
             });
         }
+
+        // Used when {@link MediaBrowserProtocol#EXTRA_MESSENGER_BINDER} is used.
+        public void unregisterCallbacks(final ServiceCallbacks callbacks) {
+            mHandler.postOrRun(new Runnable() {
+                @Override
+                public void run() {
+                    final IBinder b = callbacks.asBinder();
+                    mConnections.remove(b);
+                }
+            });
+        }
     }
 
     private class ServiceImplApi21 implements MediaBrowserServiceCompatApi21.ServiceImplApi21 {
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index 8ef49a9..27dd97f 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -17,6 +17,7 @@
 package android.support.v4.view;
 
 import android.content.res.ColorStateList;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
@@ -410,6 +411,7 @@
         float getScaleY(View view);
         float getTranslationX(View view);
         float getTranslationY(View view);
+        @Nullable Matrix getMatrix(View view);
         int getMinimumWidth(View view);
         int getMinimumHeight(View view);
         ViewPropertyAnimatorCompat animate(View view);
@@ -739,6 +741,11 @@
         }
 
         @Override
+        public Matrix getMatrix(View view) {
+            return null;
+        }
+
+        @Override
         public int getMinimumWidth(View view) {
             return ViewCompatBase.getMinimumWidth(view);
         }
@@ -1180,6 +1187,12 @@
         public float getTranslationY(View view) {
             return ViewCompatHC.getTranslationY(view);
         }
+
+        @Override
+        public Matrix getMatrix(View view) {
+            return ViewCompatHC.getMatrix(view);
+        }
+
         @Override
         public void setTranslationX(View view, float value) {
             ViewCompatHC.setTranslationX(view, value);
@@ -1686,6 +1699,16 @@
         public float getZ(View view) {
             return ViewCompatLollipop.getZ(view);
         }
+
+        @Override
+        public void offsetLeftAndRight(View view, int offset) {
+            ViewCompatLollipop.offsetLeftAndRight(view, offset);
+        }
+
+        @Override
+        public void offsetTopAndBottom(View view, int offset) {
+            ViewCompatLollipop.offsetTopAndBottom(view, offset);
+        }
     }
 
     static class MarshmallowViewCompatImpl extends LollipopViewCompatImpl {
@@ -2507,6 +2530,26 @@
     }
 
     /**
+     * The transform matrix of this view, which is calculated based on the current
+     * rotation, scale, and pivot properties.
+     * <p>
+     * Prior to 11, this method will return {@code null}.
+     *
+     * @param view The view whose Matrix will be returned
+     * @return The current transform matrix for the view
+     *
+     * @see #getRotation(View)
+     * @see #getScaleX(View)
+     * @see #getScaleY(View)
+     * @see #getPivotX(View)
+     * @see #getPivotY(View)
+     */
+    @Nullable
+    public static Matrix getMatrix(View view) {
+        return IMPL.getMatrix(view);
+    }
+
+    /**
      * Returns the minimum width of the view.
      *
      * <p>Prior to API 16 this will return 0.</p>
diff --git a/v4/java/android/support/v4/widget/DrawerLayout.java b/v4/java/android/support/v4/widget/DrawerLayout.java
index e53cbdf..e0b714e 100644
--- a/v4/java/android/support/v4/widget/DrawerLayout.java
+++ b/v4/java/android/support/v4/widget/DrawerLayout.java
@@ -1583,6 +1583,16 @@
      * @param drawerView Drawer view to open
      */
     public void openDrawer(View drawerView) {
+        openDrawer(drawerView, true);
+    }
+
+    /**
+     * Open the specified drawer view.
+     *
+     * @param drawerView Drawer view to open
+     * @param animate Whether opening of the drawer should be animated.
+     */
+    public void openDrawer(View drawerView, boolean animate) {
         if (!isDrawerView(drawerView)) {
             throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
         }
@@ -1593,7 +1603,7 @@
             lp.openState = LayoutParams.FLAG_IS_OPENED;
 
             updateChildrenImportantForAccessibility(drawerView, true);
-        } else {
+        } else if (animate) {
             lp.openState |= LayoutParams.FLAG_IS_OPENING;
 
             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
@@ -1602,6 +1612,10 @@
                 mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
                         drawerView.getTop());
             }
+        } else {
+            moveDrawerToOffset(drawerView, 1.f);
+            updateDrawerState(lp.gravity, STATE_IDLE, drawerView);
+            drawerView.setVisibility(VISIBLE);
         }
         invalidate();
     }
@@ -1613,12 +1627,23 @@
      *                GravityCompat.START or GravityCompat.END may also be used.
      */
     public void openDrawer(@EdgeGravity int gravity) {
+        openDrawer(gravity, true);
+    }
+
+    /**
+     * Open the specified drawer.
+     *
+     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
+     *                GravityCompat.START or GravityCompat.END may also be used.
+     * @param animate Whether opening of the drawer should be animated.
+     */
+    public void openDrawer(@EdgeGravity int gravity, boolean animate) {
         final View drawerView = findDrawerWithGravity(gravity);
         if (drawerView == null) {
             throw new IllegalArgumentException("No drawer view found with gravity " +
                     gravityToString(gravity));
         }
-        openDrawer(drawerView);
+        openDrawer(drawerView, animate);
     }
 
     /**
@@ -1627,6 +1652,16 @@
      * @param drawerView Drawer view to close
      */
     public void closeDrawer(View drawerView) {
+        closeDrawer(drawerView, true);
+    }
+
+    /**
+     * Close the specified drawer view.
+     *
+     * @param drawerView Drawer view to close
+     * @param animate Whether closing of the drawer should be animated.
+     */
+    public void closeDrawer(View drawerView, boolean animate) {
         if (!isDrawerView(drawerView)) {
             throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
         }
@@ -1635,7 +1670,7 @@
         if (mFirstLayout) {
             lp.onScreen = 0.f;
             lp.openState = 0;
-        } else {
+        } else if (animate) {
             lp.openState |= LayoutParams.FLAG_IS_CLOSING;
 
             if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
@@ -1644,6 +1679,10 @@
             } else {
                 mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
             }
+        } else {
+            moveDrawerToOffset(drawerView, 0.f);
+            updateDrawerState(lp.gravity, STATE_IDLE, drawerView);
+            drawerView.setVisibility(INVISIBLE);
         }
         invalidate();
     }
@@ -1655,12 +1694,23 @@
      *                GravityCompat.START or GravityCompat.END may also be used.
      */
     public void closeDrawer(@EdgeGravity int gravity) {
+        closeDrawer(gravity, true);
+    }
+
+    /**
+     * Close the specified drawer.
+     *
+     * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
+     *                GravityCompat.START or GravityCompat.END may also be used.
+     * @param animate Whether closing of the drawer should be animated.
+     */
+    public void closeDrawer(@EdgeGravity int gravity, boolean animate) {
         final View drawerView = findDrawerWithGravity(gravity);
         if (drawerView == null) {
             throw new IllegalArgumentException("No drawer view found with gravity " +
                     gravityToString(gravity));
         }
-        closeDrawer(drawerView);
+        closeDrawer(drawerView, animate);
     }
 
     /**
diff --git a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
index 9666740..0d8f106 100644
--- a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
@@ -98,16 +98,16 @@
     /** Default class name used for virtual views. */
     private static final String DEFAULT_CLASS_NAME = "android.view.View";
 
+    /** Default bounds used to determine if the client didn't set any. */
+    private static final Rect INVALID_PARENT_BOUNDS = new Rect(
+            Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
+
     // Temporary, reusable data structures.
     private final Rect mTempScreenRect = new Rect();
     private final Rect mTempParentRect = new Rect();
     private final Rect mTempVisibleRect = new Rect();
     private final int[] mTempGlobalRect = new int[2];
 
-    /** Cache of accessibility nodes. This is populated on-demand. */
-    private final SparseArrayCompat<AccessibilityNodeInfoCompat> mCachedNodes =
-            new SparseArrayCompat<>();
-
     /** System accessibility manager, used to check state and send events. */
     private final AccessibilityManager mManager;
 
@@ -370,9 +370,11 @@
      *         by this helper, or {@code false} otherwise
      */
     private boolean moveFocus(@FocusDirection int direction, @Nullable Rect previouslyFocusedRect) {
+        final SparseArrayCompat<AccessibilityNodeInfoCompat> allNodes = getAllNodes();
+
         final int focusedNodeId = mKeyboardFocusedVirtualViewId;
         final AccessibilityNodeInfoCompat focusedNode =
-                focusedNodeId == INVALID_ID ? null : mCachedNodes.get(focusedNodeId);
+                focusedNodeId == INVALID_ID ? null : allNodes.get(focusedNodeId);
 
         final AccessibilityNodeInfoCompat nextFocusedNode;
         switch (direction) {
@@ -380,7 +382,7 @@
             case View.FOCUS_BACKWARD:
                 final boolean isLayoutRtl =
                         ViewCompat.getLayoutDirection(mHost) == ViewCompat.LAYOUT_DIRECTION_RTL;
-                nextFocusedNode = FocusStrategy.findNextFocusInRelativeDirection(mCachedNodes,
+                nextFocusedNode = FocusStrategy.findNextFocusInRelativeDirection(allNodes,
                         SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, direction, isLayoutRtl,
                         false);
                 break;
@@ -402,7 +404,7 @@
                     // when moving UP or DOWN).
                     guessPreviouslyFocusedRect(mHost, direction, selectedRect);
                 }
-                nextFocusedNode = FocusStrategy.findNextFocusInAbsoluteDirection(mCachedNodes,
+                nextFocusedNode = FocusStrategy.findNextFocusInAbsoluteDirection(allNodes,
                         SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, selectedRect, direction);
                 break;
             default:
@@ -415,13 +417,26 @@
         if (nextFocusedNode == null) {
             nextFocusedNodeId = INVALID_ID;
         } else {
-            final int index = mCachedNodes.indexOfValue(nextFocusedNode);
-            nextFocusedNodeId = mCachedNodes.keyAt(index);
+            final int index = allNodes.indexOfValue(nextFocusedNode);
+            nextFocusedNodeId = allNodes.keyAt(index);
         }
 
         return requestKeyboardFocusForVirtualView(nextFocusedNodeId);
     }
 
+    private SparseArrayCompat<AccessibilityNodeInfoCompat> getAllNodes() {
+        final List<Integer> virtualViewIds = new ArrayList<>();
+        getVisibleVirtualViews(virtualViewIds);
+
+        final SparseArrayCompat<AccessibilityNodeInfoCompat> allNodes = new SparseArrayCompat<>();
+        for (int virtualViewId = 0; virtualViewId < virtualViewIds.size(); virtualViewId++) {
+            final AccessibilityNodeInfoCompat virtualView = createNodeForChild(virtualViewId);
+            allNodes.put(virtualViewId, virtualView);
+        }
+
+        return allNodes;
+    }
+
     /**
      * Obtains a best guess for the previously focused rect for keyboard focus
      * moving in the specified direction.
@@ -549,21 +564,6 @@
      *         </ul>
      */
     public final void invalidateVirtualView(int virtualViewId, int changeTypes) {
-        final SparseArrayCompat<AccessibilityNodeInfoCompat> cachedNodes = mCachedNodes;
-        if (virtualViewId == HOST_ID
-                && (changeTypes & AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
-            for (int i = 0, count = cachedNodes.size(); i < count; i++) {
-                cachedNodes.valueAt(i).recycle();
-            }
-            cachedNodes.clear();
-        } else {
-            final int index = cachedNodes.indexOfKey(virtualViewId);
-            if (index >= 0) {
-                cachedNodes.valueAt(index).recycle();
-                cachedNodes.removeAt(index);
-            }
-        }
-
         if (virtualViewId != INVALID_ID && mManager.isEnabled()) {
             final ViewParent parent = mHost.getParent();
             if (parent != null) {
@@ -718,19 +718,11 @@
      */
     @NonNull
     private AccessibilityNodeInfoCompat obtainAccessibilityNodeInfo(int virtualViewId) {
-        final AccessibilityNodeInfoCompat node;
-        final int cacheIndex = mCachedNodes.indexOfKey(virtualViewId);
-        if (cacheIndex >= 0) {
-            node = mCachedNodes.valueAt(cacheIndex);
-        } else if (virtualViewId == HOST_ID) {
-            node = createNodeForHost();
-        } else {
-            node = createNodeForChild(virtualViewId);
+        if (virtualViewId == HOST_ID) {
+            return createNodeForHost();
         }
 
-        mCachedNodes.put(virtualViewId, node);
-
-        return node;
+        return createNodeForChild(virtualViewId);
     }
 
     /**
@@ -804,6 +796,7 @@
         node.setEnabled(true);
         node.setFocusable(true);
         node.setClassName(DEFAULT_CLASS_NAME);
+        node.setBoundsInParent(INVALID_PARENT_BOUNDS);
 
         // Allow the client to populate the node.
         onPopulateNodeForVirtualView(virtualViewId, node);
@@ -815,7 +808,7 @@
         }
 
         node.getBoundsInParent(mTempParentRect);
-        if (mTempParentRect.isEmpty()) {
+        if (mTempParentRect.equals(INVALID_PARENT_BOUNDS)) {
             throw new RuntimeException("Callbacks must set parent bounds in "
                     + "populateNodeForVirtualViewId()");
         }
diff --git a/v4/java/android/support/v4/widget/NestedScrollView.java b/v4/java/android/support/v4/widget/NestedScrollView.java
index 32626c4..044c5d3 100644
--- a/v4/java/android/support/v4/widget/NestedScrollView.java
+++ b/v4/java/android/support/v4/widget/NestedScrollView.java
@@ -1647,6 +1647,8 @@
 
     @Override
     public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
         mIsLaidOut = false;
     }
 
diff --git a/v4/kitkat/android/support/v4/app/NotificationManagerCompatKitKat.java b/v4/kitkat/android/support/v4/app/NotificationManagerCompatKitKat.java
new file mode 100644
index 0000000..be97634
--- /dev/null
+++ b/v4/kitkat/android/support/v4/app/NotificationManagerCompatKitKat.java
@@ -0,0 +1,48 @@
+/*
+ * 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.v4.app;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class NotificationManagerCompatKitKat {
+    private static final String CHECK_OP_NO_THROW = "checkOpNoThrow";
+    private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
+
+    public static boolean areNotificationsEnabled(Context context) {
+        AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+        ApplicationInfo appInfo = context.getApplicationInfo();
+        String pkg = context.getApplicationContext().getPackageName();
+        int uid = appInfo.uid;
+        try {
+            Class<?> appOpsClass = Class.forName(AppOpsManager.class.getName());
+            Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE,
+                    Integer.TYPE, String.class);
+            Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
+            int value = (int) opPostNotificationValue.get(Integer.class);
+            return ((int) checkOpNoThrowMethod.invoke(appOps, value, uid, pkg)
+                    == AppOpsManager.MODE_ALLOWED);
+        } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException |
+                InvocationTargetException | IllegalAccessException | RuntimeException e) {
+            return true;
+        }
+    }
+}
diff --git a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
index 93eb3f4..3a6273e 100644
--- a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
+++ b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
@@ -204,11 +204,16 @@
      * @return A builder that will build print attributes that match the other attributes
      */
     protected PrintAttributes.Builder copyAttributes(PrintAttributes other) {
-        return (new PrintAttributes.Builder())
+        PrintAttributes.Builder b = (new PrintAttributes.Builder())
                 .setMediaSize(other.getMediaSize())
                 .setResolution(other.getResolution())
-                .setColorMode(other.getColorMode())
                 .setMinMargins(other.getMinMargins());
+
+        if (other.getColorMode() != 0) {
+            b.setColorMode(other.getColorMode());
+        }
+
+        return b;
     }
 
     /**
diff --git a/v4/tests/AndroidManifest.xml b/v4/tests/AndroidManifest.xml
index 89b2335..ca71f7a 100644
--- a/v4/tests/AndroidManifest.xml
+++ b/v4/tests/AndroidManifest.xml
@@ -28,7 +28,9 @@
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
 
-    <application android:supportsRtl="true">
+    <application
+            android:supportsRtl="true"
+            android:theme="@style/TestActivityTheme">
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.support.v4.widget.TextViewTestActivity"/>
 
diff --git a/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java b/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
index 6f8a525..0e717d8 100644
--- a/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
+++ b/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
@@ -19,7 +19,6 @@
 
 import android.content.Intent;
 import android.os.Bundle;
-import android.os.Debug;
 import android.os.Parcelable;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -27,10 +26,14 @@
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.app.test.EmptyFragmentTestActivity;
+import android.support.v4.test.R;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.Window;
+
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,8 +41,13 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-import static junit.framework.Assert.*;
-import static junit.framework.TestCase.*;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
 import static org.junit.Assert.assertNotEquals;
 
 @RunWith(AndroidJUnit4.class)
@@ -366,6 +374,91 @@
         assertTrue("child not destroyed", restoredChild.mCalledOnDestroy);
     }
 
+    @Test
+    @UiThreadTest
+    public void saveAnimationState() throws Throwable {
+        FragmentController fc = startupFragmentController(null);
+        FragmentManager fm = fc.getSupportFragmentManager();
+
+        fm.beginTransaction()
+                .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+                .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a))
+                .addToBackStack(null)
+                .commit();
+        fm.executePendingTransactions();
+
+        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
+
+        // Causes save and restore of fragments and back stack
+        fc = restartFragmentController(fc);
+        fm = fc.getSupportFragmentManager();
+
+        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
+
+        fm.beginTransaction()
+                .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0)
+                .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b))
+                .addToBackStack(null)
+                .commit();
+        fm.executePendingTransactions();
+
+        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
+
+        // Causes save and restore of fragments and back stack
+        fc = restartFragmentController(fc);
+        fm = fc.getSupportFragmentManager();
+
+        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
+
+        fm.popBackStackImmediate();
+
+        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
+
+        shutdownFragmentController(fc);
+    }
+
+    private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter,
+            int popExit) {
+        FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm;
+        BackStackRecord record = fmImpl.mBackStack.get(fmImpl.mBackStack.size() - 1);
+
+        Assert.assertEquals(enter, record.mEnterAnim);
+        Assert.assertEquals(exit, record.mExitAnim);
+        Assert.assertEquals(popEnter, record.mPopEnterAnim);
+        Assert.assertEquals(popExit, record.mPopExitAnim);
+    }
+
+    private FragmentController restartFragmentController(FragmentController fc) {
+        Parcelable savedState = shutdownFragmentController(fc);
+        return startupFragmentController(savedState);
+    }
+
+    private FragmentController startupFragmentController(Parcelable savedState) {
+        final FragmentController fc = FragmentController.createController(
+                new HostCallbacks(mActivityRule.getActivity()));
+        fc.attachHost(null);
+        fc.restoreAllState(savedState, (FragmentManagerNonConfig) null);
+        fc.dispatchCreate();
+        fc.dispatchActivityCreated();
+        fc.noteStateNotSaved();
+        fc.execPendingActions();
+        fc.doLoaderStart();
+        fc.dispatchStart();
+        fc.reportLoaderStart();
+        fc.dispatchResume();
+        fc.execPendingActions();
+        return fc;
+    }
+
+    private Parcelable shutdownFragmentController(FragmentController fc) {
+        fc.dispatchPause();
+        final Parcelable savedState = fc.saveAllState();
+        fc.dispatchStop();
+        fc.dispatchReallyStop();
+        fc.dispatchDestroy();
+        return savedState;
+    }
+
     private void executePendingTransactions(final FragmentManager fm) throws Throwable {
         mActivityRule.runOnUiThread(new Runnable() {
             @Override
@@ -495,4 +588,35 @@
             return (w != null && w.peekDecorView() != null);
         }
     }
+
+    public static class SimpleFragment extends Fragment {
+        private int mLayoutId;
+        private static final String LAYOUT_ID = "layoutId";
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState != null) {
+                mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
+            }
+        }
+
+        @Override
+        public void onSaveInstanceState(Bundle outState) {
+            super.onSaveInstanceState(outState);
+            outState.putInt(LAYOUT_ID, mLayoutId);
+        }
+
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                Bundle savedInstanceState) {
+            return inflater.inflate(mLayoutId, container, false);
+        }
+
+        public static SimpleFragment create(int layoutId) {
+            SimpleFragment fragment = new SimpleFragment();
+            fragment.mLayoutId = layoutId;
+            return fragment;
+        }
+    }
 }
diff --git a/v4/tests/java/android/support/v4/content/ContextCompatTest.java b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
index cccf892..56e89a5 100644
--- a/v4/tests/java/android/support/v4/content/ContextCompatTest.java
+++ b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
@@ -15,6 +15,9 @@
  */
 package android.support.v4.content;
 
+import org.junit.Before;
+import org.junit.Test;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
@@ -25,8 +28,7 @@
 import android.support.v4.test.R;
 import android.support.v4.testutils.TestUtils;
 import android.test.suitebuilder.annotation.SmallTest;
-import org.junit.Before;
-import org.junit.Test;
+import android.util.DisplayMetrics;
 
 import static org.junit.Assert.assertEquals;
 
@@ -103,6 +105,29 @@
         }
     }
 
+    @Test
+    public void testDrawableConfigurationWorkaround() throws Throwable {
+        final int expectedWidth = scaleFromDensity(7, DisplayMetrics.DENSITY_LOW,
+                mContext.getResources().getDisplayMetrics().densityDpi);
+
+        // Ensure we retrieve the correct drawable configuration. Specifically,
+        // this tests a workaround for a bug in drawable configuration that
+        // exists on API < 16 for references to drawables.
+        Drawable referencedDrawable = ContextCompat.getDrawable(mContext,
+                R.drawable.aliased_drawable);
+        assertEquals("Drawable configuration does not match DisplayMetrics",
+                expectedWidth, referencedDrawable.getIntrinsicWidth());
+    }
+
+    private static int scaleFromDensity(int size, int sdensity, int tdensity) {
+        if (sdensity == tdensity) {
+            return size;
+        }
+
+        // Scale by tdensity / sdensity, rounding up.
+        return ((size * tdensity) + (sdensity >> 1)) / sdensity;
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void testCheckSelfPermissionNull() {
         ContextCompat.checkSelfPermission(mContext, null);
diff --git a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
index a2ac8bd..84be6ec 100644
--- a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
@@ -34,6 +34,8 @@
 @MediumTest
 public class ViewPropertyAnimatorCompatTest extends BaseInstrumentationTestCase<VpaActivity> {
 
+    private static final int WAIT_TIMEOUT_MS = 200;
+
     private View mView;
     private int mNumListenerCalls = 0;
 
@@ -80,9 +82,7 @@
             }
         });
         assertTrue(latch2.await(200, TimeUnit.MILLISECONDS));
-        // Now sleep to allow second listener callback to happen, if it will
-        Thread.sleep(200);
-        assertEquals(1, mNumListenerCalls);
+        waitAndCheckCallCount(1);
     }
 
     @Test
@@ -118,8 +118,15 @@
             }
         });
         assertTrue(latch2.await(200, TimeUnit.MILLISECONDS));
-        // Now sleep to allow second listener callback to happen, if it will
-        Thread.sleep(200);
-        assertEquals(1, mNumListenerCalls);
+        waitAndCheckCallCount(1);
+    }
+
+    void waitAndCheckCallCount(final int count) throws InterruptedException {
+        int timeLeft = WAIT_TIMEOUT_MS;
+        while (mNumListenerCalls != count) {
+            Thread.sleep(20);
+            timeLeft -= 20;
+            assertTrue(timeLeft > 0);
+        }
     }
 }
diff --git a/v4/tests/res/anim/fade_in.xml b/v4/tests/res/anim/fade_in.xml
new file mode 100644
index 0000000..92d5bbe
--- /dev/null
+++ b/v4/tests/res/anim/fade_in.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+       android:duration="300"
+       android:fromAlpha="0.0"
+       android:toAlpha="1.0"/>
diff --git a/v4/tests/res/anim/fade_out.xml b/v4/tests/res/anim/fade_out.xml
new file mode 100644
index 0000000..bc5a2ab
--- /dev/null
+++ b/v4/tests/res/anim/fade_out.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+       android:duration="300"
+       android:fromAlpha="1.0"
+       android:toAlpha="0.0"/>
diff --git a/v4/tests/res/drawable-ldpi/aliased_drawable_alternate.png b/v4/tests/res/drawable-ldpi/aliased_drawable_alternate.png
new file mode 100644
index 0000000..909b23a
--- /dev/null
+++ b/v4/tests/res/drawable-ldpi/aliased_drawable_alternate.png
Binary files differ
diff --git a/v4/tests/res/values/drawables.xml b/v4/tests/res/values/drawables.xml
new file mode 100644
index 0000000..b2955a5
--- /dev/null
+++ b/v4/tests/res/values/drawables.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<resources>
+    <item type="drawable" name="aliased_drawable">@drawable/aliased_drawable_alternate</item>
+</resources>
diff --git a/v4/tests/res/values/styles.xml b/v4/tests/res/values/styles.xml
index c0855d8..447d5ec 100644
--- a/v4/tests/res/values/styles.xml
+++ b/v4/tests/res/values/styles.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <style name="TestActivityTheme">
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
     <style name="TextMediumStyle" parent="@android:style/TextAppearance.Medium">
         <item name="android:textSize">@dimen/text_medium_size</item>
         <item name="android:textColor">@color/text_color</item>
diff --git a/v7/appcompat/Android.mk b/v7/appcompat/Android.mk
index 6816f54..6d11fcb 100644
--- a/v7/appcompat/Android.mk
+++ b/v7/appcompat/Android.mk
@@ -15,19 +15,25 @@
 LOCAL_PATH := $(call my-dir)
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v7-appcompat \
+#       android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-appcompat
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-vectordrawable \
-        android-support-animatedvectordrawable
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-vectordrawable \
+    android-support-animatedvectordrawable
 LOCAL_JAVA_LIBRARIES := android-support-v4
-LOCAL_AAPT_FLAGS += --auto-add-overlay \
-        --no-version-vectors
+LOCAL_AAPT_FLAGS := --no-version-vectors
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/v7/appcompat/res/drawable/abc_btn_check_material.xml b/v7/appcompat/res/drawable/abc_btn_check_material.xml
index f8e90a9..0ebbf00 100644
--- a/v7/appcompat/res/drawable/abc_btn_check_material.xml
+++ b/v7/appcompat/res/drawable/abc_btn_check_material.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:drawable="@drawable/abc_btn_checkbox_checked_mtrl" />
-    <item android:drawable="@drawable/abc_btn_checkbox_unchecked_mtrl" />
-</selector>
\ No newline at end of file
+<!-- This is a dummy drawable so that we can refer to the drawable ID -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@android:color/white"/>
+</shape>
diff --git a/v7/appcompat/res/drawable/abc_btn_radio_material.xml b/v7/appcompat/res/drawable/abc_btn_radio_material.xml
index ac4eead..f20add7 100644
--- a/v7/appcompat/res/drawable/abc_btn_radio_material.xml
+++ b/v7/appcompat/res/drawable/abc_btn_radio_material.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:drawable="@drawable/abc_btn_radio_on_mtrl" />
-    <item android:drawable="@drawable/abc_btn_radio_off_mtrl" />
-</selector>
\ No newline at end of file
+<!-- This is a dummy drawable so that we can refer to the drawable ID -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@android:color/white"/>
+</shape>
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
index 4529093..893e1b5 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -18,7 +18,6 @@
 
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.annotation.CallSuper;
@@ -33,9 +32,7 @@
 import android.support.v4.app.TaskStackBuilder;
 import android.support.v4.view.KeyEventCompat;
 import android.support.v7.view.ActionMode;
-import android.support.v7.widget.TintResources;
 import android.support.v7.widget.Toolbar;
-import android.util.DisplayMetrics;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -64,7 +61,6 @@
     private AppCompatDelegate mDelegate;
     private int mThemeId = 0;
     private boolean mEatKeyUpEvent;
-    private Resources mResources;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -159,12 +155,6 @@
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         getDelegate().onConfigurationChanged(newConfig);
-        if (mResources != null) {
-            // The real (and thus managed) resources object was already updated
-            // by ResourcesManager, so pull the current metrics from there.
-            final DisplayMetrics newMetrics = super.getResources().getDisplayMetrics();
-            mResources.updateConfiguration(newConfig, newMetrics);
-        }
     }
 
     @Override
@@ -532,12 +522,4 @@
         }
         return super.dispatchKeyEvent(event);
     }
-
-    @Override
-    public Resources getResources() {
-        if (mResources == null) {
-            mResources = new TintResources(this, super.getResources());
-        }
-        return mResources;
-    }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index 43e1f7b..52368c9 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -300,7 +300,6 @@
 
         if (mActionBar != null) {
             mActionBar.onDestroy();
-            mActionBar = null;
         }
     }
 
@@ -971,8 +970,7 @@
 
         return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                 isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
-                true, /* Read read app:theme as a fallback at all times for legacy reasons */
-                isPre21 /* Only tint wrap the context pre-L */
+                true /* Read read app:theme as a fallback at all times for legacy reasons */
         );
     }
 
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java b/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
index dcf855c..7477813 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
@@ -39,7 +39,6 @@
 import android.support.v7.widget.AppCompatSeekBar;
 import android.support.v7.widget.AppCompatSpinner;
 import android.support.v7.widget.AppCompatTextView;
-import android.support.v7.widget.TintContextWrapper;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.InflateException;
@@ -79,7 +78,7 @@
 
     public final View createView(View parent, final String name, @NonNull Context context,
             @NonNull AttributeSet attrs, boolean inheritContext,
-            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
+            boolean readAndroidTheme, boolean readAppTheme) {
         final Context originalContext = context;
 
         // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
@@ -91,9 +90,6 @@
             // We then apply the theme on the context, if specified
             context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
         }
-        if (wrapContext) {
-            context = TintContextWrapper.wrap(context);
-        }
 
         View view = null;
 
diff --git a/v7/appcompat/src/android/support/v7/view/StandaloneActionMode.java b/v7/appcompat/src/android/support/v7/view/StandaloneActionMode.java
index 7f4c87e..38dbb02 100644
--- a/v7/appcompat/src/android/support/v7/view/StandaloneActionMode.java
+++ b/v7/appcompat/src/android/support/v7/view/StandaloneActionMode.java
@@ -129,7 +129,7 @@
 
     @Override
     public MenuInflater getMenuInflater() {
-        return new MenuInflater(mContextView.getContext());
+        return new SupportMenuInflater(mContextView.getContext());
     }
 
     public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
index 4a2345e..2fa93fb 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Drawable.ConstantState;
 import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.StateListDrawable;
 import android.os.Build;
 import android.support.annotation.ColorInt;
 import android.support.annotation.DrawableRes;
@@ -162,9 +163,9 @@
     private ArrayMap<String, InflateDelegate> mDelegates;
     private SparseArray<String> mKnownDrawableIdTags;
 
-    private final Object mDelegateDrawableCacheLock = new Object();
+    private final Object mDrawableCacheLock = new Object();
     private final WeakHashMap<Context, LongSparseArray<WeakReference<Drawable.ConstantState>>>
-            mDelegateDrawableCaches = new WeakHashMap<>(0);
+            mDrawableCaches = new WeakHashMap<>(0);
 
     private TypedValue mTypedValue;
 
@@ -180,8 +181,12 @@
 
         Drawable drawable = loadDrawableFromDelegates(context, resId);
         if (drawable == null) {
+            drawable = createDrawableIfNeeded(context, resId);
+        }
+        if (drawable == null) {
             drawable = ContextCompat.getDrawable(context, resId);
         }
+
         if (drawable != null) {
             // Tint it if needed
             drawable = tintDrawable(context, resId, failIfNotKnown, drawable);
@@ -193,6 +198,56 @@
         return drawable;
     }
 
+    private static long createCacheKey(TypedValue tv) {
+        return (((long) tv.assetCookie) << 32) | tv.data;
+    }
+
+    private Drawable createDrawableIfNeeded(@NonNull Context context,
+            @DrawableRes final int resId) {
+        if (mTypedValue == null) {
+            mTypedValue = new TypedValue();
+        }
+        final TypedValue tv = mTypedValue;
+        context.getResources().getValue(resId, tv, true);
+        final long key = createCacheKey(tv);
+
+        Drawable dr = getCachedDrawable(context, key);
+        if (dr != null) {
+            // If we got a cached drawable, return it
+            return dr;
+        }
+
+        // Else we need to try and create one...
+        if (resId == R.drawable.abc_cab_background_top_material) {
+            dr = new LayerDrawable(new Drawable[]{
+                    getDrawable(context, R.drawable.abc_cab_background_internal_bg),
+                    getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha)
+            });
+        } else if (resId == R.drawable.abc_btn_check_material) {
+            final StateListDrawable sld = new StateListDrawable();
+            sld.addState(ThemeUtils.CHECKED_STATE_SET,
+                    getDrawable(context, R.drawable.abc_btn_checkbox_checked_mtrl));
+            sld.addState(ThemeUtils.EMPTY_STATE_SET,
+                    getDrawable(context, R.drawable.abc_btn_checkbox_unchecked_mtrl));
+            dr = sld;
+        } else if (resId == R.drawable.abc_btn_radio_material) {
+            final StateListDrawable sld = new StateListDrawable();
+            sld.addState(ThemeUtils.CHECKED_STATE_SET,
+                    getDrawable(context, R.drawable.abc_btn_radio_on_mtrl));
+            sld.addState(ThemeUtils.EMPTY_STATE_SET,
+                    getDrawable(context, R.drawable.abc_btn_radio_off_mtrl));
+            dr = sld;
+        }
+
+        if (dr != null) {
+            dr.setChangingConfigurations(tv.changingConfigurations);
+            // If we reached here then we created a new drawable, add it to the cache
+            addDrawableToCache(context, key, dr);
+        }
+
+        return dr;
+    }
+
     private Drawable tintDrawable(@NonNull Context context, @DrawableRes int resId,
             boolean failIfNotKnown, @NonNull Drawable drawable) {
         final ColorStateList tintList = getTintList(context, resId);
@@ -209,11 +264,6 @@
             if (tintMode != null) {
                 DrawableCompat.setTintMode(drawable, tintMode);
             }
-        } else if (resId == R.drawable.abc_cab_background_top_material) {
-            return new LayerDrawable(new Drawable[]{
-                    getDrawable(context, R.drawable.abc_cab_background_internal_bg),
-                    getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha)
-            });
         } else if (resId == R.drawable.abc_seekbar_track_material) {
             LayerDrawable ld = (LayerDrawable) drawable;
             setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
@@ -266,14 +316,13 @@
             if (mTypedValue == null) {
                 mTypedValue = new TypedValue();
             }
-
             final TypedValue tv = mTypedValue;
             final Resources res = context.getResources();
             res.getValue(resId, tv, true);
 
-            final long key = (((long) tv.assetCookie) << 32) | tv.data;
+            final long key = createCacheKey(tv);
 
-            Drawable dr = getCachedDelegateDrawable(context, key);
+            Drawable dr = getCachedDrawable(context, key);
             if (dr != null) {
                 if (DEBUG) {
                     Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " +
@@ -310,7 +359,7 @@
                     if (dr != null) {
                         // Add it to the drawable cache
                         dr.setChangingConfigurations(tv.changingConfigurations);
-                        if (addCachedDelegateDrawable(context, key, dr) && DEBUG) {
+                        if (addDrawableToCache(context, key, dr) && DEBUG) {
                             Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " +
                                     context.getResources().getResourceName(resId));
                         }
@@ -330,10 +379,10 @@
         return null;
     }
 
-    private Drawable getCachedDelegateDrawable(@NonNull final Context context, final long key) {
-        synchronized (mDelegateDrawableCacheLock) {
+    private Drawable getCachedDrawable(@NonNull final Context context, final long key) {
+        synchronized (mDrawableCacheLock) {
             final LongSparseArray<WeakReference<ConstantState>> cache
-                    = mDelegateDrawableCaches.get(context);
+                    = mDrawableCaches.get(context);
             if (cache == null) {
                 return null;
             }
@@ -353,16 +402,15 @@
         return null;
     }
 
-    private boolean addCachedDelegateDrawable(@NonNull final Context context, final long key,
+    private boolean addDrawableToCache(@NonNull final Context context, final long key,
             @NonNull final Drawable drawable) {
         final ConstantState cs = drawable.getConstantState();
         if (cs != null) {
-            synchronized (mDelegateDrawableCacheLock) {
-                LongSparseArray<WeakReference<ConstantState>> cache
-                        = mDelegateDrawableCaches.get(context);
+            synchronized (mDrawableCacheLock) {
+                LongSparseArray<WeakReference<ConstantState>> cache = mDrawableCaches.get(context);
                 if (cache == null) {
                     cache = new LongSparseArray<>();
-                    mDelegateDrawableCaches.put(context, cache);
+                    mDrawableCaches.put(context, cache);
                 }
                 cache.put(key, new WeakReference<ConstantState>(cs));
             }
@@ -371,19 +419,7 @@
         return false;
     }
 
-    public final Drawable onDrawableLoadedFromResources(@NonNull Context context,
-            @NonNull TintResources resources, @DrawableRes final int resId) {
-        Drawable drawable = loadDrawableFromDelegates(context, resId);
-        if (drawable == null) {
-            drawable = resources.superGetDrawable(resId);
-        }
-        if (drawable != null) {
-            return tintDrawable(context, resId, false, drawable);
-        }
-        return null;
-    }
-
-    private static boolean tintDrawableUsingColorFilter(@NonNull Context context,
+    static boolean tintDrawableUsingColorFilter(@NonNull Context context,
             @DrawableRes final int resId, @NonNull Drawable drawable) {
         PorterDuff.Mode tintMode = DEFAULT_MODE;
         boolean colorAttrSet = false;
diff --git a/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java b/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java
new file mode 100644
index 0000000..828a792
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 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.widget;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Movie;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This extends Resources but delegates the calls to another Resources object. This enables
+ * any customization done by some subclass of Resources to be also picked up.
+ */
+class ResourcesWrapper extends Resources {
+
+    private final Resources mResources;
+
+    public ResourcesWrapper(Resources resources) {
+        super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
+        mResources = resources;
+    }
+
+    @Override
+    public CharSequence getText(int id) throws NotFoundException {
+        return mResources.getText(id);
+    }
+
+    @Override
+    public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+        return mResources.getQuantityText(id, quantity);
+    }
+
+    @Override
+    public String getString(int id) throws NotFoundException {
+        return mResources.getString(id);
+    }
+
+    @Override
+    public String getString(int id, Object... formatArgs) throws NotFoundException {
+        return mResources.getString(id, formatArgs);
+    }
+
+    @Override
+    public String getQuantityString(int id, int quantity, Object... formatArgs)
+            throws NotFoundException {
+        return mResources.getQuantityString(id, quantity, formatArgs);
+    }
+
+    @Override
+    public String getQuantityString(int id, int quantity) throws NotFoundException {
+        return mResources.getQuantityString(id, quantity);
+    }
+
+    @Override
+    public CharSequence getText(int id, CharSequence def) {
+        return mResources.getText(id, def);
+    }
+
+    @Override
+    public CharSequence[] getTextArray(int id) throws NotFoundException {
+        return mResources.getTextArray(id);
+    }
+
+    @Override
+    public String[] getStringArray(int id) throws NotFoundException {
+        return mResources.getStringArray(id);
+    }
+
+    @Override
+    public int[] getIntArray(int id) throws NotFoundException {
+        return mResources.getIntArray(id);
+    }
+
+    @Override
+    public TypedArray obtainTypedArray(int id) throws NotFoundException {
+        return mResources.obtainTypedArray(id);
+    }
+
+    @Override
+    public float getDimension(int id) throws NotFoundException {
+        return mResources.getDimension(id);
+    }
+
+    @Override
+    public int getDimensionPixelOffset(int id) throws NotFoundException {
+        return mResources.getDimensionPixelOffset(id);
+    }
+
+    @Override
+    public int getDimensionPixelSize(int id) throws NotFoundException {
+        return mResources.getDimensionPixelSize(id);
+    }
+
+    @Override
+    public float getFraction(int id, int base, int pbase) {
+        return mResources.getFraction(id, base, pbase);
+    }
+
+    @Override
+    public Drawable getDrawable(int id) throws NotFoundException {
+        return mResources.getDrawable(id);
+    }
+
+    @Override
+    public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
+        return mResources.getDrawable(id, theme);
+    }
+
+    @Override
+    public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
+        return mResources.getDrawableForDensity(id, density);
+    }
+
+    @Override
+    public Drawable getDrawableForDensity(int id, int density, Theme theme) {
+        return mResources.getDrawableForDensity(id, density, theme);
+    }
+
+    @Override
+    public Movie getMovie(int id) throws NotFoundException {
+        return mResources.getMovie(id);
+    }
+
+    @Override
+    public int getColor(int id) throws NotFoundException {
+        return mResources.getColor(id);
+    }
+
+    @Override
+    public ColorStateList getColorStateList(int id) throws NotFoundException {
+        return mResources.getColorStateList(id);
+    }
+
+    @Override
+    public boolean getBoolean(int id) throws NotFoundException {
+        return mResources.getBoolean(id);
+    }
+
+    @Override
+    public int getInteger(int id) throws NotFoundException {
+        return mResources.getInteger(id);
+    }
+
+    @Override
+    public XmlResourceParser getLayout(int id) throws NotFoundException {
+        return mResources.getLayout(id);
+    }
+
+    @Override
+    public XmlResourceParser getAnimation(int id) throws NotFoundException {
+        return mResources.getAnimation(id);
+    }
+
+    @Override
+    public XmlResourceParser getXml(int id) throws NotFoundException {
+        return mResources.getXml(id);
+    }
+
+    @Override
+    public InputStream openRawResource(int id) throws NotFoundException {
+        return mResources.openRawResource(id);
+    }
+
+    @Override
+    public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+        return mResources.openRawResource(id, value);
+    }
+
+    @Override
+    public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+        return mResources.openRawResourceFd(id);
+    }
+
+    @Override
+    public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+            throws NotFoundException {
+        mResources.getValue(id, outValue, resolveRefs);
+    }
+
+    @Override
+    public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
+            throws NotFoundException {
+        mResources.getValueForDensity(id, density, outValue, resolveRefs);
+    }
+
+    @Override
+    public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+            throws NotFoundException {
+        mResources.getValue(name, outValue, resolveRefs);
+    }
+
+    @Override
+    public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
+        return mResources.obtainAttributes(set, attrs);
+    }
+
+    @Override
+    public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
+        super.updateConfiguration(config, metrics);
+        if (mResources != null) { // called from super's constructor. So, need to check.
+            mResources.updateConfiguration(config, metrics);
+        }
+    }
+
+    @Override
+    public DisplayMetrics getDisplayMetrics() {
+        return mResources.getDisplayMetrics();
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        return mResources.getConfiguration();
+    }
+
+    @Override
+    public int getIdentifier(String name, String defType, String defPackage) {
+        return mResources.getIdentifier(name, defType, defPackage);
+    }
+
+    @Override
+    public String getResourceName(int resid) throws NotFoundException {
+        return mResources.getResourceName(resid);
+    }
+
+    @Override
+    public String getResourcePackageName(int resid) throws NotFoundException {
+        return mResources.getResourcePackageName(resid);
+    }
+
+    @Override
+    public String getResourceTypeName(int resid) throws NotFoundException {
+        return mResources.getResourceTypeName(resid);
+    }
+
+    @Override
+    public String getResourceEntryName(int resid) throws NotFoundException {
+        return mResources.getResourceEntryName(resid);
+    }
+
+    @Override
+    public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
+            throws XmlPullParserException, IOException {
+        mResources.parseBundleExtras(parser, outBundle);
+    }
+
+    @Override
+    public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle)
+            throws XmlPullParserException {
+        mResources.parseBundleExtra(tagName, attrs, outBundle);
+    }
+}
+
diff --git a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
index 6647f89..119890d 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
@@ -27,15 +27,13 @@
 /**
  * A {@link android.content.ContextWrapper} which returns a tint-aware
  * {@link android.content.res.Resources} instance from {@link #getResources()}.
- *
- * @hide
  */
-public class TintContextWrapper extends ContextWrapper {
+class TintContextWrapper extends ContextWrapper {
 
     private static final ArrayList<WeakReference<TintContextWrapper>> sCache = new ArrayList<>();
 
     public static Context wrap(@NonNull final Context context) {
-        if (shouldWrap(context)) {
+        if (!(context instanceof TintContextWrapper)) {
             // First check our instance cache
             for (int i = 0, count = sCache.size(); i < count; i++) {
                 final WeakReference<TintContextWrapper> ref = sCache.get(i);
@@ -54,39 +52,10 @@
         return context;
     }
 
-    private static boolean shouldWrap(@NonNull final Context context) {
-        if (context instanceof TintContextWrapper) {
-            // If the Context is already a TintContextWrapper, no needed to wrap again
-            return false;
-        }
-        if (context.getResources() instanceof TintResources) {
-            // If the Context already has a TintResources impl, no needed to wrap again
-            return false;
-        }
-        // Else, we should wrap
-        return true;
-    }
-
     private Resources mResources;
-    private final Resources.Theme mTheme;
 
-    private TintContextWrapper(@NonNull final Context base) {
+    private TintContextWrapper(Context base) {
         super(base);
-
-        // We need to create a copy of the Theme so that the Theme references our Resources
-        // instance
-        mTheme = getResources().newTheme();
-        mTheme.setTo(base.getTheme());
-    }
-
-    @Override
-    public Resources.Theme getTheme() {
-        return mTheme;
-    }
-
-    @Override
-    public void setTheme(int resid) {
-        mTheme.applyStyle(resid, true);
     }
 
     @Override
diff --git a/v7/appcompat/src/android/support/v7/widget/TintResources.java b/v7/appcompat/src/android/support/v7/widget/TintResources.java
index 6739c93..39e03a5 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintResources.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintResources.java
@@ -25,14 +25,13 @@
 
 /**
  * This class allows us to intercept calls so that we can tint resources (if applicable).
- *
- * @hide
  */
-public class TintResources extends Resources {
+class TintResources extends ResourcesWrapper {
+
     private final WeakReference<Context> mContextRef;
 
-    public TintResources(@NonNull final Context context, @NonNull final Resources res) {
-        super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
+    public TintResources(@NonNull Context context, @NonNull final Resources res) {
+        super(res);
         mContextRef = new WeakReference<>(context);
     }
 
@@ -43,15 +42,11 @@
      */
     @Override
     public Drawable getDrawable(int id) throws NotFoundException {
-        final Context context = mContextRef.get();
-        if (context != null) {
-            return AppCompatDrawableManager.get().onDrawableLoadedFromResources(context, this, id);
-        } else {
-            return super.getDrawable(id);
+        Drawable d = super.getDrawable(id);
+        Context context = mContextRef.get();
+        if (d != null && context != null) {
+            AppCompatDrawableManager.get().tintDrawableUsingColorFilter(context, id, d);
         }
-    }
-
-    final Drawable superGetDrawable(int id) {
-        return super.getDrawable(id);
+        return d;
     }
 }
\ No newline at end of file
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
index 49f3efe..ff63834 100755
--- a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
@@ -80,6 +80,20 @@
 
     @Test
     @MediumTest
+    public void testDrawerOpenCloseNoAnimationViaAPI() {
+        assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+        for (int i = 0; i < 5; i++) {
+            onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+            assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+            onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+            assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+        }
+    }
+
+    @Test
+    @MediumTest
     public void testDrawerOpenCloseWithRedundancyViaAPI() {
         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
 
@@ -104,6 +118,30 @@
 
     @Test
     @MediumTest
+    public void testDrawerOpenCloseNoAnimationWithRedundancyViaAPI() {
+        assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+        for (int i = 0; i < 5; i++) {
+            onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+            assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+            // Try opening the drawer when it's already opened
+            onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+            assertTrue("Opened drawer is still opened #" + i,
+                    mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+            onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+            assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+            // Try closing the drawer when it's already closed
+            onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+            assertFalse("Closed drawer is still closed #" + i,
+                    mDrawerLayout.isDrawerOpen(GravityCompat.START));
+        }
+    }
+
+    @Test
+    @MediumTest
     public void testDrawerOpenCloseViaSwipes() {
         assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
 
@@ -239,6 +277,35 @@
 
     @Test
     @SmallTest
+    public void testDrawerListenerCallbacksOnOpeningNoAnimationViaAPI() {
+        // Register a mock listener
+        DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+        mDrawerLayout.addDrawerListener(mockedListener);
+
+        // Open the drawer so it becomes visible
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+
+        // We expect that our listener has been notified that the drawer has been opened
+        // with the reference to our drawer
+        verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
+        // We expect that our listener has not been notified that the drawer has been closed
+        verify(mockedListener, never()).onDrawerClosed(any(View.class));
+
+        verify(mockedListener, times(1)).onDrawerSlide(any(View.class), eq(1f));
+
+        // Request to open the drawer again
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+
+        // We expect that our listener has not been notified again that the drawer has been opened
+        verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
+        // We expect that our listener has not been notified that the drawer has been closed
+        verify(mockedListener, never()).onDrawerClosed(any(View.class));
+
+        mDrawerLayout.removeDrawerListener(mockedListener);
+    }
+
+    @Test
+    @SmallTest
     public void testDrawerListenerCallbacksOnClosingViaAPI() {
         // Open the drawer so it becomes visible
         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
@@ -279,6 +346,38 @@
 
     @Test
     @SmallTest
+    public void testDrawerListenerCallbacksOnClosingNoAnimationViaAPI() {
+        // Open the drawer so it becomes visible
+        onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+
+        // Register a mock listener
+        DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+        mDrawerLayout.addDrawerListener(mockedListener);
+
+        // Close the drawer
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+
+        // We expect that our listener has not been notified that the drawer has been opened
+        verify(mockedListener, never()).onDrawerOpened(any(View.class));
+        // We expect that our listener has been notified that the drawer has been closed
+        // with the reference to our drawer
+        verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
+
+        verify(mockedListener, times(1)).onDrawerSlide(any(View.class), eq(0f));
+
+        // Attempt to close the drawer again.
+        onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+
+        // We expect that our listener has not been notified that the drawer has been opened
+        verify(mockedListener, never()).onDrawerOpened(any(View.class));
+        // We expect that our listener has not been notified again that the drawer has been closed
+        verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
+
+        mDrawerLayout.removeDrawerListener(mockedListener);
+    }
+
+    @Test
+    @SmallTest
     public void testDrawerListenerCallbacksOnOpeningViaSwipes() {
         // Register a mock listener
         DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java b/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java
index 4feb013..8e839b9 100755
--- a/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java
@@ -144,6 +144,31 @@
     /**
      * Opens the drawer at the specified edge gravity.
      */
+    public static ViewAction openDrawer(final int drawerEdgeGravity, final boolean animate) {
+        return wrap(new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(DrawerLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Opens the drawer";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                DrawerLayout drawerLayout = (DrawerLayout) view;
+                drawerLayout.openDrawer(drawerEdgeGravity, animate);
+            }
+        });
+    }
+
+    /**
+     * Opens the drawer at the specified edge gravity.
+     */
     public static ViewAction openDrawer(final int drawerEdgeGravity) {
         return wrap(new ViewAction() {
             @Override
@@ -217,6 +242,31 @@
     }
 
     /**
+     * Closes the drawer at the specified edge gravity.
+     */
+    public static ViewAction closeDrawer(final int drawerEdgeGravity, final boolean animate) {
+        return wrap(new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(DrawerLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Closes the drawer";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                DrawerLayout drawerLayout = (DrawerLayout) view;
+                drawerLayout.closeDrawer(drawerEdgeGravity, animate);
+            }
+        });
+    }
+
+    /**
      * Closes the drawer.
      */
     public static ViewAction closeDrawer(final View drawerView) {
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/TintResourcesTest.java b/v7/appcompat/tests/src/android/support/v7/widget/TintResourcesTest.java
new file mode 100644
index 0000000..81cac57
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/TintResourcesTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.widget;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.BaseInstrumentationTestCase;
+
+import org.junit.Test;
+
+public class TintResourcesTest extends BaseInstrumentationTestCase<AppCompatActivity> {
+
+    public TintResourcesTest() {
+        super(AppCompatActivity.class);
+    }
+
+    @Test
+    public void testTintResourcesDelegateBackToOriginalResources() {
+        final TestResources testResources = new TestResources(getActivity().getResources());
+        // First make sure that the flag is false
+        assertFalse(testResources.wasGetDrawableCalled());
+
+        // Now wrap in a TintResources instance and get a Drawable
+        final Resources tintResources = new TintResources(getActivity(), testResources);
+        tintResources.getDrawable(android.R.drawable.ic_delete);
+
+        // ...and assert that the flag was flipped
+        assertTrue(testResources.wasGetDrawableCalled());
+    }
+
+    /**
+     * Special Resources class which returns a known Drawable instance from a special ID
+     */
+    private static class TestResources extends Resources {
+        private boolean mGetDrawableCalled;
+
+        public TestResources(Resources res) {
+            super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
+        }
+
+        @Override
+        public Drawable getDrawable(int id) throws NotFoundException {
+            mGetDrawableCalled = true;
+            return super.getDrawable(id);
+        }
+
+        public boolean wasGetDrawableCalled() {
+            return mGetDrawableCalled;
+        }
+    }
+
+}
diff --git a/v7/cardview/Android.mk b/v7/cardview/Android.mk
index 7fa5cec..cc51239 100644
--- a/v7/cardview/Android.mk
+++ b/v7/cardview/Android.mk
@@ -19,6 +19,7 @@
 # SDK version than the resources.  The resources library and the R class that it
 # contains will not be linked into the final static library.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-cardview-res
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
@@ -72,9 +73,8 @@
 LOCAL_MODULE := android-support-v7-cardview-api21
 LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base \
-    android-support-v7-cardview-jellybean-mr1
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res  \
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-jellybean-mr1
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res \
     android-support-annotations
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -82,17 +82,23 @@
 support_module_src_files += $(LOCAL_SRC_FILES)
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v7-cardview
+#
 # in their makefiles to include the resources in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-cardview
 LOCAL_SDK_VERSION := 7
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-api21
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res \
-    android-support-annotations
+LOCAL_JAVA_LIBRARIES := android-support-annotations
+LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
index c4af30b..85fc178 100644
--- a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
+++ b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
@@ -24,10 +24,11 @@
 
     @Override
     public void initialize(CardViewDelegate cardView, Context context,
-            ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
-        final RoundRectDrawable backgroundDrawable = new RoundRectDrawable(backgroundColor, radius);
-        cardView.setBackgroundDrawable(backgroundDrawable);
-        View view = (View) cardView;
+                ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
+        final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
+        cardView.setCardBackground(background);
+
+        View view = cardView.getCardView();
         view.setClipToOutline(true);
         view.setElevation(elevation);
         setMaxElevation(cardView, maxElevation);
@@ -35,7 +36,7 @@
 
     @Override
     public void setRadius(CardViewDelegate cardView, float radius) {
-        ((RoundRectDrawable) (cardView.getBackground())).setRadius(radius);
+        getCardBackground(cardView).setRadius(radius);
     }
 
     @Override
@@ -44,14 +45,14 @@
 
     @Override
     public void setMaxElevation(CardViewDelegate cardView, float maxElevation) {
-        ((RoundRectDrawable) (cardView.getBackground())).setPadding(maxElevation,
+        getCardBackground(cardView).setPadding(maxElevation,
                 cardView.getUseCompatPadding(), cardView.getPreventCornerOverlap());
         updatePadding(cardView);
     }
 
     @Override
     public float getMaxElevation(CardViewDelegate cardView) {
-        return ((RoundRectDrawable) (cardView.getBackground())).getPadding();
+        return getCardBackground(cardView).getPadding();
     }
 
     @Override
@@ -66,17 +67,17 @@
 
     @Override
     public float getRadius(CardViewDelegate cardView) {
-        return ((RoundRectDrawable) (cardView.getBackground())).getRadius();
+        return getCardBackground(cardView).getRadius();
     }
 
     @Override
     public void setElevation(CardViewDelegate cardView, float elevation) {
-        ((View) cardView).setElevation(elevation);
+        cardView.getCardView().setElevation(elevation);
     }
 
     @Override
     public float getElevation(CardViewDelegate cardView) {
-        return ((View) cardView).getElevation();
+        return cardView.getCardView().getElevation();
     }
 
     @Override
@@ -106,6 +107,10 @@
 
     @Override
     public void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color) {
-        ((RoundRectDrawable) (cardView.getBackground())).setColor(color);
+        getCardBackground(cardView).setColor(color);
+    }
+
+    private RoundRectDrawable getCardBackground(CardViewDelegate cardView) {
+        return ((RoundRectDrawable) cardView.getCardBackground());
     }
 }
\ No newline at end of file
diff --git a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
index 68fd714..5a9a462 100644
--- a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
+++ b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
@@ -50,7 +50,7 @@
     private ColorStateList mBackground;
     private PorterDuffColorFilter mTintFilter;
     private ColorStateList mTint;
-    private PorterDuff.Mode mTintMode;
+    private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN;
 
     public RoundRectDrawable(ColorStateList backgroundColor, float radius) {
         mRadius = radius;
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java b/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
index 8752b7d..b5be921 100644
--- a/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
+++ b/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
@@ -15,8 +15,8 @@
  */
 package android.support.v7.widget;
 
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.view.View;
 
 /**
  * Interface provided by CardView to implementations.
@@ -24,11 +24,11 @@
  * Necessary to resolve circular dependency between base CardView and platform implementations.
  */
 interface CardViewDelegate {
-    void setBackgroundDrawable(Drawable paramDrawable);
-    Drawable getBackground();
+    void setCardBackground(Drawable drawable);
+    Drawable getCardBackground();
     boolean getUseCompatPadding();
     boolean getPreventCornerOverlap();
-    float getRadius();
     void setShadowPadding(int left, int top, int right, int bottom);
     void setMinWidthHeightInternal(int width, int height);
+    View getCardView();
 }
\ No newline at end of file
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
index 384dbfd..5738251 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
@@ -81,12 +81,13 @@
         RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
                 elevation, maxElevation);
         background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
-        cardView.setBackgroundDrawable(background);
+        cardView.setCardBackground(background);
         updatePadding(cardView);
     }
 
-    RoundRectDrawableWithShadow createBackground(Context context, ColorStateList backgroundColor,
-            float radius, float elevation, float maxElevation) {
+    private RoundRectDrawableWithShadow createBackground(Context context,
+                    ColorStateList backgroundColor, float radius, float elevation,
+                    float maxElevation) {
         return new RoundRectDrawableWithShadow(context.getResources(), backgroundColor, radius,
                 elevation, maxElevation);
     }
@@ -160,6 +161,6 @@
     }
 
     private RoundRectDrawableWithShadow getShadowBackground(CardViewDelegate cardView) {
-        return ((RoundRectDrawableWithShadow) cardView.getBackground());
+        return ((RoundRectDrawableWithShadow) cardView.getCardBackground());
     }
 }
diff --git a/v7/cardview/src/android/support/v7/widget/CardView.java b/v7/cardview/src/android/support/v7/widget/CardView.java
index 68b262f..899e480 100644
--- a/v7/cardview/src/android/support/v7/widget/CardView.java
+++ b/v7/cardview/src/android/support/v7/widget/CardView.java
@@ -21,10 +21,13 @@
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.support.annotation.ColorInt;
 import android.support.annotation.Nullable;
 import android.support.v7.cardview.R;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.FrameLayout;
 
 /**
@@ -70,7 +73,7 @@
  * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingRight
  * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingBottom
  */
-public class CardView extends FrameLayout implements CardViewDelegate {
+public class CardView extends FrameLayout {
 
     private static final int[] COLOR_BACKGROUND_ATTR = {android.R.attr.colorBackground};
     private static final CardViewImpl IMPL;
@@ -103,7 +106,6 @@
 
     private final Rect mShadowBounds = new Rect();
 
-
     public CardView(Context context) {
         super(context);
         initialize(context, null, 0);
@@ -134,7 +136,6 @@
      * @return <code>true</code> if CardView adds inner padding on platforms Lollipop and after to
      * have same dimensions with platforms before Lollipop.
      */
-    @Override
     public boolean getUseCompatPadding() {
         return mCompatPadding;
     }
@@ -156,11 +157,10 @@
      * @attr ref android.support.v7.cardview.R.styleable#CardView_cardUseCompatPadding
      */
     public void setUseCompatPadding(boolean useCompatPadding) {
-        if (mCompatPadding == useCompatPadding) {
-            return;
+        if (mCompatPadding != useCompatPadding) {
+            mCompatPadding = useCompatPadding;
+            IMPL.onCompatPaddingChanged(mCardViewDelegate);
         }
-        mCompatPadding = useCompatPadding;
-        IMPL.onCompatPaddingChanged(this);
     }
 
     /**
@@ -181,7 +181,7 @@
      */
     public void setContentPadding(int left, int top, int right, int bottom) {
         mContentPadding.set(left, top, right, bottom);
-        IMPL.updatePadding(this);
+        IMPL.updatePadding(mCardViewDelegate);
     }
 
     @Override
@@ -191,7 +191,7 @@
             switch (widthMode) {
                 case MeasureSpec.EXACTLY:
                 case MeasureSpec.AT_MOST:
-                    final int minWidth = (int) Math.ceil(IMPL.getMinWidth(this));
+                    final int minWidth = (int) Math.ceil(IMPL.getMinWidth(mCardViewDelegate));
                     widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minWidth,
                             MeasureSpec.getSize(widthMeasureSpec)), widthMode);
                     break;
@@ -201,7 +201,7 @@
             switch (heightMode) {
                 case MeasureSpec.EXACTLY:
                 case MeasureSpec.AT_MOST:
-                    final int minHeight = (int) Math.ceil(IMPL.getMinHeight(this));
+                    final int minHeight = (int) Math.ceil(IMPL.getMinHeight(mCardViewDelegate));
                     heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minHeight,
                             MeasureSpec.getSize(heightMeasureSpec)), heightMode);
                     break;
@@ -252,7 +252,8 @@
         mUserSetMinHeight = a.getDimensionPixelSize(R.styleable.CardView_android_minHeight, 0);
         a.recycle();
 
-        IMPL.initialize(this, context, backgroundColor, radius, elevation, maxElevation);
+        IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
+                elevation, maxElevation);
     }
 
     @Override
@@ -268,29 +269,13 @@
     }
 
     /**
-     * Internal method used by CardView implementations to set min width / height that is necessary
-     * to draw shadows on APIs older than 21.
-     *
-     * @hide
-     */
-    @Override
-    public void setMinWidthHeightInternal(int width, int height) {
-        if (width > mUserSetMinWidth) {
-            super.setMinimumWidth(width);
-        }
-        if (height > mUserSetMinHeight) {
-            super.setMinimumHeight(height);
-        }
-    }
-
-    /**
      * Updates the background color of the CardView
      *
      * @param color The new color to set for the card background
      * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
      */
-    public void setCardBackgroundColor(int color) {
-        IMPL.setBackgroundColor(this, ColorStateList.valueOf(color));
+    public void setCardBackgroundColor(@ColorInt int color) {
+        IMPL.setBackgroundColor(mCardViewDelegate, ColorStateList.valueOf(color));
     }
 
     /**
@@ -300,7 +285,7 @@
      * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
      */
     public void setCardBackgroundColor(@Nullable ColorStateList color) {
-        IMPL.setBackgroundColor(this, color);
+        IMPL.setBackgroundColor(mCardViewDelegate, color);
     }
 
     /**
@@ -347,7 +332,7 @@
      * @see #setRadius(float)
      */
     public void setRadius(float radius) {
-        IMPL.setRadius(this, radius);
+        IMPL.setRadius(mCardViewDelegate, radius);
     }
 
     /**
@@ -357,19 +342,7 @@
      * @see #getRadius()
      */
     public float getRadius() {
-        return IMPL.getRadius(this);
-    }
-
-    /**
-     * Internal method used by CardView implementations to update the padding.
-     *
-     * @hide
-     */
-    @Override
-    public void setShadowPadding(int left, int top, int right, int bottom) {
-        mShadowBounds.set(left, top, right, bottom);
-        super.setPadding(left + mContentPadding.left, top + mContentPadding.top,
-                right + mContentPadding.right, bottom + mContentPadding.bottom);
+        return IMPL.getRadius(mCardViewDelegate);
     }
 
     /**
@@ -381,7 +354,7 @@
      * @see #setMaxCardElevation(float)
      */
     public void setCardElevation(float elevation) {
-        IMPL.setElevation(this, elevation);
+        IMPL.setElevation(mCardViewDelegate, elevation);
     }
 
     /**
@@ -392,7 +365,7 @@
      * @see #getMaxCardElevation()
      */
     public float getCardElevation() {
-        return IMPL.getElevation(this);
+        return IMPL.getElevation(mCardViewDelegate);
     }
 
     /**
@@ -407,7 +380,7 @@
      * @see #getMaxCardElevation()
      */
     public void setMaxCardElevation(float maxElevation) {
-        IMPL.setMaxElevation(this, maxElevation);
+        IMPL.setMaxElevation(mCardViewDelegate, maxElevation);
     }
 
     /**
@@ -418,7 +391,7 @@
      * @see #getCardElevation()
      */
     public float getMaxCardElevation() {
-        return IMPL.getMaxElevation(this);
+        return IMPL.getMaxElevation(mCardViewDelegate);
     }
 
     /**
@@ -428,7 +401,6 @@
      * @return True if CardView prevents overlaps with rounded corners on platforms before Lollipop.
      *         Default value is <code>true</code>.
      */
-    @Override
     public boolean getPreventCornerOverlap() {
         return mPreventCornerOverlap;
     }
@@ -447,10 +419,56 @@
      * @see #setUseCompatPadding(boolean)
      */
     public void setPreventCornerOverlap(boolean preventCornerOverlap) {
-        if (preventCornerOverlap == mPreventCornerOverlap) {
-            return;
+        if (preventCornerOverlap != mPreventCornerOverlap) {
+            mPreventCornerOverlap = preventCornerOverlap;
+            IMPL.onPreventCornerOverlapChanged(mCardViewDelegate);
         }
-        mPreventCornerOverlap = preventCornerOverlap;
-        IMPL.onPreventCornerOverlapChanged(this);
     }
+
+    private final CardViewDelegate mCardViewDelegate = new CardViewDelegate() {
+        private Drawable mCardBackground;
+
+        @Override
+        public void setCardBackground(Drawable drawable) {
+            mCardBackground = drawable;
+            setBackgroundDrawable(drawable);
+        }
+
+        @Override
+        public boolean getUseCompatPadding() {
+            return CardView.this.getUseCompatPadding();
+        }
+
+        @Override
+        public boolean getPreventCornerOverlap() {
+            return CardView.this.getPreventCornerOverlap();
+        }
+
+        @Override
+        public void setShadowPadding(int left, int top, int right, int bottom) {
+            mShadowBounds.set(left, top, right, bottom);
+            CardView.super.setPadding(left + mContentPadding.left, top + mContentPadding.top,
+                    right + mContentPadding.right, bottom + mContentPadding.bottom);
+        }
+
+        @Override
+        public void setMinWidthHeightInternal(int width, int height) {
+            if (width > mUserSetMinWidth) {
+                CardView.super.setMinimumWidth(width);
+            }
+            if (height > mUserSetMinHeight) {
+                CardView.super.setMinimumHeight(height);
+            }
+        }
+
+        @Override
+        public Drawable getCardBackground() {
+            return mCardBackground;
+        }
+
+        @Override
+        public View getCardView() {
+            return CardView.this;
+        }
+    };
 }
diff --git a/v7/gridlayout/Android.mk b/v7/gridlayout/Android.mk
index 4617db6..f9bd0a1 100644
--- a/v7/gridlayout/Android.mk
+++ b/v7/gridlayout/Android.mk
@@ -15,16 +15,23 @@
 LOCAL_PATH := $(call my-dir)
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v7-gridlayout \
+#       android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-gridlayout
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SDK_VERSION := 7
-LOCAL_JAVA_LIBRARIES := android-support-v4
+LOCAL_SHARED_ANDROID_LIBRARIES := android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 
diff --git a/v7/mediarouter/Android.mk b/v7/mediarouter/Android.mk
index ada74c5..c0c4085 100644
--- a/v7/mediarouter/Android.mk
+++ b/v7/mediarouter/Android.mk
@@ -16,17 +16,14 @@
 
 # Build the resources using the latest applicable SDK version.
 # We do this here because the final static library must be compiled with an older
-# SDK version than the resources.  The resources library and the R class that it
-# contains will not be linked into the final static library.
+# SDK version than the resources.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-mediarouter-res
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
-	frameworks/support/v7/appcompat/res
-LOCAL_AAPT_FLAGS := \
-	--auto-add-overlay \
-	--extra-packages android.support.v7.appcompat
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := android-support-v7-appcompat
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -77,18 +74,30 @@
 support_module_src_files += $(LOCAL_SRC_FILES)
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v7-mediarouter \
+#       android-support-v7-appcompat \
+#       android-support-v7-palette \
+#       android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-mediarouter
 LOCAL_SDK_VERSION := 7
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-api24
-LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-v7-mediarouter-res \
+LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v7-mediarouter-res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
     android-support-v7-appcompat \
-    android-support-v7-palette
+    android-support-v7-palette \
+    android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v7/mediarouter/api/current.txt b/v7/mediarouter/api/current.txt
index e1d6ad6..f65dd85 100644
--- a/v7/mediarouter/api/current.txt
+++ b/v7/mediarouter/api/current.txt
@@ -702,7 +702,7 @@
     method public void resume(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
     method public void seek(java.lang.String, long, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
     method public void sendMessage(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
-    method public void setMessageCallback(android.support.v7.media.RemotePlaybackClient.MessageCallback);
+    method public void setOnMessageReceivedListener(android.support.v7.media.RemotePlaybackClient.OnMessageReceivedListener);
     method public void setSessionId(java.lang.String);
     method public void setStatusCallback(android.support.v7.media.RemotePlaybackClient.StatusCallback);
     method public void startSession(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
@@ -719,9 +719,8 @@
     method public void onResult(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus, java.lang.String, android.support.v7.media.MediaItemStatus);
   }
 
-  public static abstract class RemotePlaybackClient.MessageCallback {
-    ctor public RemotePlaybackClient.MessageCallback();
-    method public void onMessageReceived(java.lang.String, android.os.Bundle);
+  public static abstract interface RemotePlaybackClient.OnMessageReceivedListener {
+    method public abstract void onMessageReceived(java.lang.String, android.os.Bundle);
   }
 
   public static abstract class RemotePlaybackClient.SessionActionCallback extends android.support.v7.media.RemotePlaybackClient.ActionCallback {
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
index a7aafd2..a7a0dd3 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
@@ -42,7 +42,7 @@
     }
 
     public MediaRouteVolumeSlider(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.seekBarStyle);
+        this(context, attrs, android.support.v7.appcompat.R.attr.seekBarStyle);
     }
 
     public MediaRouteVolumeSlider(Context context, AttributeSet attrs, int defStyleAttr) {
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
index 25b1d1d..3d55951 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
@@ -84,7 +84,8 @@
     }
 
     public static @ControllerColorType int getControllerColor(Context context, int style) {
-        int primaryColor = getThemeColor(context, style, R.attr.colorPrimary);
+        int primaryColor = getThemeColor(context, style,
+                android.support.v7.appcompat.R.attr.colorPrimary);
         if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
                 >= MIN_CONTRAST) {
             return COLOR_WHITE_ON_DARK_BACKGROUND;
@@ -93,20 +94,23 @@
     }
 
     public static int getButtonTextColor(Context context) {
-        int primaryColor = getThemeColor(context, 0, R.attr.colorPrimary);
+        int primaryColor = getThemeColor(context, 0,
+                android.support.v7.appcompat.R.attr.colorPrimary);
         int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
 
         if (ColorUtils.calculateContrast(primaryColor, backgroundColor) < MIN_CONTRAST) {
             // Default to colorAccent if the contrast ratio is low.
-            return getThemeColor(context, 0, R.attr.colorAccent);
+            return getThemeColor(context, 0, android.support.v7.appcompat.R.attr.colorAccent);
         }
         return primaryColor;
     }
 
     public static void setMediaControlsBackgroundColor(
             Context context, View mainControls, View groupControls, boolean hasGroup) {
-        int primaryColor = getThemeColor(context, 0, R.attr.colorPrimary);
-        int primaryDarkColor = getThemeColor(context, 0, R.attr.colorPrimaryDark);
+        int primaryColor = getThemeColor(context, 0,
+                android.support.v7.appcompat.R.attr.colorPrimary);
+        int primaryDarkColor = getThemeColor(context, 0,
+                android.support.v7.appcompat.R.attr.colorPrimaryDark);
         if (hasGroup && getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
             // Instead of showing dark controls in a possibly dark (i.e. the primary dark), model
             // the white dialog and use the primary color for the group controls.
@@ -135,7 +139,8 @@
 
     private static boolean isLightTheme(Context context) {
         TypedValue value = new TypedValue();
-        return context.getTheme().resolveAttribute(R.attr.isLightTheme, value, true)
+        return context.getTheme().resolveAttribute(
+                android.support.v7.appcompat.R.attr.isLightTheme, value, true)
                 && value.data != 0;
     }
 
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
index 55734c9..c827534 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
@@ -893,7 +893,7 @@
     public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
 
     /**
-     * Custom media control action: Send {link #EXTRA_MESSAGE}.
+     * Custom media control action: Send {@link #EXTRA_MESSAGE}.
      * <p>
      * This action asks a route to handle a message described by EXTRA_MESSAGE.
      * </p>
@@ -1178,6 +1178,8 @@
      * {@link #EXTRA_MESSAGE_RECEIVER message receivers} to describe a message between a
      * session and a media route provider.
      * </p><p>
+     * The value is a {@link android.os.Bundle}.
+     * </p>
      */
     public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
 
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index cc0f75e..343d21b 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -141,6 +141,14 @@
         return null;
     }
 
+    @Override
+    public boolean onUnbind(Intent intent) {
+        if (mProvider != null) {
+            mProvider.setCallback(null);
+        }
+        return super.onUnbind(intent);
+    }
+
     private boolean onRegisterClient(Messenger messenger, int requestId, int version) {
         if (version >= CLIENT_VERSION_1) {
             int index = findClient(messenger);
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index a321925..896f057 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -408,7 +408,14 @@
         }
         checkCallingThread();
 
-        sGlobal.selectRoute(getDefaultRoute(), reason);
+        // Choose the fallback route if it's not already selected.
+        // Otherwise, select the default route.
+        RouteInfo fallbackRoute = sGlobal.chooseFallbackRoute();
+        if (sGlobal.getSelectedRoute() != fallbackRoute) {
+            sGlobal.selectRoute(fallbackRoute, reason);
+        } else {
+            sGlobal.selectRoute(sGlobal.getDefaultRoute(), reason);
+        }
     }
 
     /**
@@ -2430,7 +2437,7 @@
             }
         }
 
-        private RouteInfo chooseFallbackRoute() {
+        RouteInfo chooseFallbackRoute() {
             // When the current route is removed or no longer selectable,
             // we want to revert to a live audio route if there is
             // one (usually Bluetooth A2DP).  Failing that, use
diff --git a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
index 113f55d..1716af3 100644
--- a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
+++ b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
@@ -52,7 +52,7 @@
 
     private String mSessionId;
     private StatusCallback mStatusCallback;
-    private MessageCallback mMessageCallback;
+    private OnMessageReceivedListener mOnMessageReceivedListener;
 
     /**
      * Creates a remote playback client for a route.
@@ -250,10 +250,10 @@
      * The callback should be set before the session is created.
      * </p>
      *
-     * @param callback The callback to set.  May be null to remove the previous callback.
+     * @param listener The callback to set.  May be null to remove the previous callback.
      */
-    public void setMessageCallback(MessageCallback callback) {
-        mMessageCallback = callback;
+    public void setOnMessageReceivedListener(OnMessageReceivedListener listener) {
+        mOnMessageReceivedListener = listener;
     }
 
     /**
@@ -573,7 +573,7 @@
      * more information about the semantics of this request.
      * </p>
      *
-     * @param message A bundle message denoting {link MediaControlIntent#EXTRA_SESSION_MESSAGE}.
+     * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
      * @param callback A callback to invoke when the request has been processed, or null if none.
      *
      * @throws IllegalStateException if there is no current session.
@@ -933,8 +933,8 @@
                     Log.d(TAG, "Received message callback: sessionId=" + sessionId);
                 }
 
-                if (mMessageCallback != null) {
-                    mMessageCallback.onMessageReceived(sessionId,
+                if (mOnMessageReceivedListener != null) {
+                    mOnMessageReceivedListener.onMessageReceived(sessionId,
                             intent.getBundleExtra(MediaControlIntent.EXTRA_MESSAGE));
                 }
             }
@@ -979,17 +979,6 @@
         }
     }
 
-    public static abstract class MessageCallback {
-        /**
-         * Called when a message received.
-         *
-         * @param sessionId The session id.
-         * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
-         */
-        public void onMessageReceived(String sessionId, Bundle message) {
-        }
-    }
-
     /**
      * Base callback type for remote playback requests.
      */
@@ -1038,4 +1027,17 @@
         public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
         }
     }
+
+    /**
+     * A callback that will receive messages from media sessions.
+     */
+    public interface OnMessageReceivedListener {
+        /**
+         * Called when a message received.
+         *
+         * @param sessionId The session id.
+         * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
+         */
+        void onMessageReceived(String sessionId, Bundle message);
+    }
 }
diff --git a/v7/palette/Android.mk b/v7/palette/Android.mk
index 0c4cb05..814ef24 100644
--- a/v7/palette/Android.mk
+++ b/v7/palette/Android.mk
@@ -15,13 +15,24 @@
 LOCAL_PATH := $(call my-dir)
 
 # Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v7-palette \
+#       android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-palette
 LOCAL_SDK_VERSION := 7
 LOCAL_SRC_FILES := $(call all-java-files-under, src/main)
-LOCAL_MANIFEST_FILE := $(LOCAL_PATH)/src/main/AndroidManifest.xml
-LOCAL_JAVA_LIBRARIES += android-support-v4
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_SHARED_ANDROID_LIBRARIES += android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/v7/preference/Android.mk b/v7/preference/Android.mk
index 85ded02..43fc74c 100644
--- a/v7/preference/Android.mk
+++ b/v7/preference/Android.mk
@@ -14,40 +14,48 @@
 
 LOCAL_PATH := $(call my-dir)
 
-# Build the resources using the latest applicable SDK version.
-# We do this here because the final static library must be compiled with an older
-# SDK version than the resources.  The resources library and the R class that it
-# contains will not be linked into the final static library.
+# Build the resources separately because the constants built with the resources need to access
+# the latest SDK but the actual code needs to build against SDK 7.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-preference-res
-LOCAL_SRC_FILES := $(call all-java-files-under, constants)
 LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_RESOURCE_DIR := \
-        frameworks/support/v7/appcompat/res \
-        frameworks/support/v7/recyclerview/res \
-        $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
-	--auto-add-overlay
+LOCAL_SRC_FILES := $(call all-java-files-under,constants)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v7-appcompat \
+    android-support-v7-recyclerview
 LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v7-preference \
+#       android-support-v7-appcompat \
+#       android-support-v7-recyclerview \
+#       android-support-v4 \
+#       android-support-annotations
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-preference
 LOCAL_SDK_VERSION := 7
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
-# LOCAL_STATIC_JAVA_LIBRARIES :=
-LOCAL_JAVA_LIBRARIES := \
-        android-support-v4 \
-        android-support-v7-appcompat \
-        android-support-v7-recyclerview \
-        android-support-annotations \
-        android-support-v7-preference-res
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+    android-support-v7-preference-res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v7-appcompat \
+    android-support-v7-recyclerview \
+    android-support-v4 \
+    android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # API Check
diff --git a/v7/preference/api/current.txt b/v7/preference/api/current.txt
index b2046ca..c2b5b4f 100644
--- a/v7/preference/api/current.txt
+++ b/v7/preference/api/current.txt
@@ -159,6 +159,7 @@
     method public void setSummary(int);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitle(int);
+    method public void setViewId(int);
     method public final void setVisible(boolean);
     method public void setWidgetLayoutResource(int);
     method public boolean shouldDisableDependents();
diff --git a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
index 50ab1db..8ef625b 100644
--- a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
+++ b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
@@ -109,7 +109,7 @@
             return;
         }
 
-        View checkboxView = view.findViewById(R.id.checkbox);
+        View checkboxView = view.findViewById(android.support.v7.appcompat.R.id.checkbox);
         syncCheckboxView(checkboxView);
 
         View summaryView = view.findViewById(android.R.id.summary);
diff --git a/v7/preference/src/android/support/v7/preference/DropDownPreference.java b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
index e7a0514..ee1e723 100644
--- a/v7/preference/src/android/support/v7/preference/DropDownPreference.java
+++ b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
@@ -25,6 +25,10 @@
 import android.widget.ArrayAdapter;
 import android.widget.Spinner;
 
+/**
+ * A version of {@link ListPreference} that presents the options in a
+ * drop down menu rather than a dialog.
+ */
 public class DropDownPreference extends ListPreference {
 
     private final Context mContext;
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index 175f855..d9621a2 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -101,6 +101,7 @@
     private OnPreferenceClickListener mOnClickListener;
 
     private int mOrder = DEFAULT_ORDER;
+    private int mViewId = 0;
     private CharSequence mTitle;
     private CharSequence mSummary;
     /**
@@ -477,6 +478,7 @@
      */
     public void onBindViewHolder(PreferenceViewHolder holder) {
         holder.itemView.setOnClickListener(mClickListener);
+        holder.itemView.setId(mViewId);
 
         final TextView titleView = (TextView) holder.findViewById(android.R.id.title);
         if (titleView != null) {
@@ -583,6 +585,16 @@
     }
 
     /**
+     * Set the ID that will be assigned to the overall View representing this
+     * preference, once bound.
+     *
+     * @see View#setId(int)
+     */
+    public void setViewId(int viewId) {
+        mViewId = viewId;
+    }
+
+    /**
      * Sets the title for this Preference with a CharSequence.
      * This title will be placed into the ID
      * {@link android.R.id#title} within the View bound by
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
index ecc6712..afb966c 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
@@ -764,11 +764,6 @@
             final int width = parent.getWidth();
             for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
                 final View view = parent.getChildAt(childViewIndex);
-                if (shouldDrawDividerAbove(view, parent)) {
-                    int top = (int) ViewCompat.getY(view);
-                    mDivider.setBounds(0, top, width, top + mDividerHeight);
-                    mDivider.draw(c);
-                }
                 if (shouldDrawDividerBelow(view, parent)) {
                     int top = (int) ViewCompat.getY(view) + view.getHeight();
                     mDivider.setBounds(0, top, width, top + mDividerHeight);
@@ -780,32 +775,27 @@
         @Override
         public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
                 RecyclerView.State state) {
-            if (shouldDrawDividerAbove(view, parent)) {
-                outRect.top = mDividerHeight;
-            }
             if (shouldDrawDividerBelow(view, parent)) {
                 outRect.bottom = mDividerHeight;
             }
         }
 
-        private boolean shouldDrawDividerAbove(View view, RecyclerView parent) {
-            final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
-            return holder.getAdapterPosition() == 0 &&
-                    ((PreferenceViewHolder) holder).isDividerAllowedAbove();
-        }
-
         private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
-            final PreferenceViewHolder holder =
-                    (PreferenceViewHolder) parent.getChildViewHolder(view);
+            final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
+            final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
+                    && ((PreferenceViewHolder) holder).isDividerAllowedBelow();
+            if (!dividerAllowedBelow) {
+                return false;
+            }
             boolean nextAllowed = true;
             int index = parent.indexOfChild(view);
             if (index < parent.getChildCount() - 1) {
                 final View nextView = parent.getChildAt(index + 1);
-                final PreferenceViewHolder nextHolder =
-                        (PreferenceViewHolder) parent.getChildViewHolder(nextView);
-                nextAllowed = nextHolder.isDividerAllowedAbove();
+                final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
+                nextAllowed = nextHolder instanceof PreferenceViewHolder
+                        && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
             }
-            return nextAllowed && holder.isDividerAllowedBelow();
+            return nextAllowed;
         }
 
         public void setDivider(Drawable divider) {
diff --git a/v7/recyclerview/Android.mk b/v7/recyclerview/Android.mk
index 1a0819c..021296e 100644
--- a/v7/recyclerview/Android.mk
+++ b/v7/recyclerview/Android.mk
@@ -14,34 +14,26 @@
 
 LOCAL_PATH := $(call my-dir)
 
-# # Build the resources using the latest applicable SDK version.
-# # We do this here because the final static library must be compiled with an older
-# # SDK version than the resources.  The resources library and the R class that it
-# # contains will not be linked into the final static library.
-include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-v7-recyclerview-res
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
-	--auto-add-overlay
-LOCAL_JAR_EXCLUDE_FILES := none
-LOCAL_JAVA_LANGUAGE_VERSION := 1.7
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 # Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-v7-recycler-view \
+#       android-support-v4 \
+#       android-support-annotations
+#
+# in their makefiles to include the resources and their dependencies in their package.
 include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
 LOCAL_MODULE := android-support-v7-recyclerview
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_SDK_VERSION := 7
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_JAVA_LIBRARIES := \
-        android-support-v4 \
-        android-support-annotations \
-        android-support-v7-recyclerview-res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-v4 \
+    android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index 56c86aa..9a5c15b 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -240,6 +240,8 @@
     method public abstract int getStartAfterPadding();
     method public abstract int getTotalSpace();
     method public int getTotalSpaceChange();
+    method public abstract int getTransformedEndWithDecoration(android.view.View);
+    method public abstract int getTransformedStartWithDecoration(android.view.View);
     method public abstract void offsetChild(android.view.View, int);
     method public abstract void offsetChildren(int);
     method public void onLayoutComplete();
@@ -285,6 +287,7 @@
     method public android.support.v7.widget.RecyclerView.LayoutManager getLayoutManager();
     method public int getMaxFlingVelocity();
     method public int getMinFlingVelocity();
+    method public boolean getPreserveFocusAfterLayout();
     method public android.support.v7.widget.RecyclerView.RecycledViewPool getRecycledViewPool();
     method public int getScrollState();
     method public boolean hasFixedSize();
@@ -315,6 +318,7 @@
     method public void setLayoutFrozen(boolean);
     method public void setLayoutManager(android.support.v7.widget.RecyclerView.LayoutManager);
     method public deprecated void setOnScrollListener(android.support.v7.widget.RecyclerView.OnScrollListener);
+    method public void setPreserveFocusAfterLayout(boolean);
     method public void setRecycledViewPool(android.support.v7.widget.RecyclerView.RecycledViewPool);
     method public void setRecyclerListener(android.support.v7.widget.RecyclerView.RecyclerListener);
     method public void setScrollingTouchSlop(int);
@@ -515,6 +519,7 @@
     method public int getRowCountForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
     method public int getSelectionModeForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
     method public int getTopDecorationHeight(android.view.View);
+    method public void getTransformedBoundingBox(android.view.View, boolean, android.graphics.Rect);
     method public int getWidth();
     method public int getWidthMode();
     method public boolean hasFocus();
@@ -550,6 +555,7 @@
     method public void onItemsUpdated(android.support.v7.widget.RecyclerView, int, int);
     method public void onItemsUpdated(android.support.v7.widget.RecyclerView, int, int, java.lang.Object);
     method public void onLayoutChildren(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+    method public void onLayoutCompleted(android.support.v7.widget.RecyclerView.State);
     method public void onMeasure(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State, int, int);
     method public deprecated boolean onRequestChildFocus(android.support.v7.widget.RecyclerView, android.view.View, android.view.View);
     method public boolean onRequestChildFocus(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State, android.view.View, android.view.View);
diff --git a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
index b1dd91c..47e9722 100644
--- a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
@@ -502,6 +502,9 @@
      * @return True if updates should be processed.
      */
     boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+        if (itemCount < 1) {
+            return false;
+        }
         mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
         mExistingUpdateTypes |= UpdateOp.UPDATE;
         return mPendingUpdates.size() == 1;
@@ -511,6 +514,9 @@
      * @return True if updates should be processed.
      */
     boolean onItemRangeInserted(int positionStart, int itemCount) {
+        if (itemCount < 1) {
+            return false;
+        }
         mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
         mExistingUpdateTypes |= UpdateOp.ADD;
         return mPendingUpdates.size() == 1;
@@ -520,6 +526,9 @@
      * @return True if updates should be processed.
      */
     boolean onItemRangeRemoved(int positionStart, int itemCount) {
+        if (itemCount < 1) {
+            return false;
+        }
         mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
         mExistingUpdateTypes |= UpdateOp.REMOVE;
         return mPendingUpdates.size() == 1;
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index ef100cb..6a56418 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -172,9 +172,12 @@
             validateChildOrder();
         }
         clearPreLayoutSpanMappingCache();
-        if (!state.isPreLayout()) {
-            mPendingSpanCountChange = false;
-        }
+    }
+
+    @Override
+    public void onLayoutCompleted(RecyclerView.State state) {
+        super.onLayoutCompleted(state);
+        mPendingSpanCountChange = false;
     }
 
     private void clearPreLayoutSpanMappingCache() {
@@ -800,6 +803,7 @@
         }
         mSpanCount = spanCount;
         mSpanSizeLookup.invalidateSpanIndexCache();
+        requestLayout();
     }
 
     /**
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index 7a7f0f9..0570135 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.v4.view.ViewCompat;
@@ -619,17 +620,22 @@
         }
         layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
         if (!state.isPreLayout()) {
-            mPendingScrollPosition = NO_POSITION;
-            mPendingScrollPositionOffset = INVALID_OFFSET;
             mOrientationHelper.onLayoutComplete();
         }
         mLastStackFromEnd = mStackFromEnd;
-        mPendingSavedState = null; // we don't need this anymore
         if (DEBUG) {
             validateChildOrder();
         }
     }
 
+    @Override
+    public void onLayoutCompleted(RecyclerView.State state) {
+        super.onLayoutCompleted(state);
+        mPendingSavedState = null; // we don't need this anymore
+        mPendingScrollPosition = NO_POSITION;
+        mPendingScrollPositionOffset = INVALID_OFFSET;
+    }
+
     /**
      * Method called when Anchor position is decided. Extending class can setup accordingly or
      * even update anchor info if necessary.
@@ -1227,6 +1233,8 @@
 
     /**
      * Recycles views that went out of bounds after scrolling towards the end of the layout.
+     * <p>
+     * Checks both layout position and visible position to guarantee that the view is not visible.
      *
      * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
      * @param dt       This can be used to add additional padding to the visible area. This is used
@@ -1247,7 +1255,9 @@
         if (mShouldReverseLayout) {
             for (int i = childCount - 1; i >= 0; i--) {
                 View child = getChildAt(i);
-                if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
+                if (mOrientationHelper.getDecoratedEnd(child) > limit
+                        || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
+                    // stop here
                     recycleChildren(recycler, childCount - 1, i);
                     return;
                 }
@@ -1255,7 +1265,9 @@
         } else {
             for (int i = 0; i < childCount; i++) {
                 View child = getChildAt(i);
-                if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
+                if (mOrientationHelper.getDecoratedEnd(child) > limit
+                        || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
+                    // stop here
                     recycleChildren(recycler, 0, i);
                     return;
                 }
@@ -1266,6 +1278,8 @@
 
     /**
      * Recycles views that went out of bounds after scrolling towards the start of the layout.
+     * <p>
+     * Checks both layout position and visible position to guarantee that the view is not visible.
      *
      * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
      * @param dt       This can be used to add additional padding to the visible area. This is used
@@ -1285,7 +1299,9 @@
         if (mShouldReverseLayout) {
             for (int i = 0; i < childCount; i++) {
                 View child = getChildAt(i);
-                if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
+                if (mOrientationHelper.getDecoratedStart(child) < limit
+                        || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
+                    // stop here
                     recycleChildren(recycler, 0, i);
                     return;
                 }
@@ -1293,7 +1309,9 @@
         } else {
             for (int i = childCount - 1; i >= 0; i--) {
                 View child = getChildAt(i);
-                if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
+                if (mOrientationHelper.getDecoratedStart(child) < limit
+                        || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
+                    // stop here
                     recycleChildren(recycler, childCount - 1, i);
                     return;
                 }
diff --git a/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
index cd1f5e1..8987b9c 100644
--- a/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.widget;
 
+import android.graphics.Rect;
 import android.view.View;
 import android.widget.LinearLayout;
 
@@ -41,6 +42,8 @@
 
     private int mLastTotalSpace = INVALID_SIZE;
 
+    final Rect mTmpRect = new Rect();
+
     private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
         mLayoutManager = layoutManager;
     }
@@ -93,6 +96,38 @@
     public abstract int getDecoratedEnd(View view);
 
     /**
+     * Returns the end of the View after its matrix transformations are applied to its layout
+     * position.
+     * <p>
+     * This method is useful when trying to detect the visible edge of a View.
+     * <p>
+     * It includes the decorations but does not include the margins.
+     *
+     * @param view The view whose transformed end will be returned
+     * @return The end of the View after its decor insets and transformation matrix is applied to
+     * its position
+     *
+     * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+     */
+    public abstract int getTransformedEndWithDecoration(View view);
+
+    /**
+     * Returns the start of the View after its matrix transformations are applied to its layout
+     * position.
+     * <p>
+     * This method is useful when trying to detect the visible edge of a View.
+     * <p>
+     * It includes the decorations but does not include the margins.
+     *
+     * @param view The view whose transformed start will be returned
+     * @return The start of the View after its decor insets and transformation matrix is applied to
+     * its position
+     *
+     * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+     */
+    public abstract int getTransformedStartWithDecoration(View view);
+
+    /**
      * Returns the space occupied by this View in the current orientation including decorations and
      * margins.
      *
@@ -265,6 +300,18 @@
             }
 
             @Override
+            public int getTransformedEndWithDecoration(View view) {
+                mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+                return mTmpRect.right;
+            }
+
+            @Override
+            public int getTransformedStartWithDecoration(View view) {
+                mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+                return mTmpRect.left;
+            }
+
+            @Override
             public int getTotalSpace() {
                 return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
                         - mLayoutManager.getPaddingRight();
@@ -351,6 +398,18 @@
             }
 
             @Override
+            public int getTransformedEndWithDecoration(View view) {
+                mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+                return mTmpRect.bottom;
+            }
+
+            @Override
+            public int getTransformedStartWithDecoration(View view) {
+                mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+                return mTmpRect.top;
+            }
+
+            @Override
             public int getTotalSpace() {
                 return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
                         - mLayoutManager.getPaddingBottom();
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index d9a1362..c9b672a 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -21,8 +21,10 @@
 import android.content.res.TypedArray;
 import android.database.Observable;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -58,7 +60,6 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.View.MeasureSpec;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
@@ -296,6 +297,7 @@
     };
 
     private final Rect mTempRect = new Rect();
+    private final RectF mTempRectF = new RectF();
     private Adapter mAdapter;
     @VisibleForTesting LayoutManager mLayout;
     private RecyclerListener mRecyclerListener;
@@ -376,6 +378,7 @@
     private final int mMaxFlingVelocity;
     // This value is used when handling generic motion events.
     private float mScrollFactor = Float.MIN_VALUE;
+    private boolean mPreserveFocusAfterLayout = true;
 
     private final ViewFlinger mViewFlinger = new ViewFlinger();
 
@@ -1446,11 +1449,7 @@
      * This method consumes all deferred changes to avoid that case.
      */
     private void consumePendingUpdateOperations() {
-        if (!mFirstLayoutComplete) {
-            // a layout request will happen, we should not do layout here.
-            return;
-        }
-        if (mDataSetHasChangedAfterLayout) {
+        if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
             TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
             dispatchLayout();
             TraceCompat.endSection();
@@ -2917,6 +2916,75 @@
         dispatchLayoutStep3();
     }
 
+    private void saveFocusInfo() {
+        View child = null;
+        if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) {
+            child = getFocusedChild();
+        }
+
+        final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child);
+        if (focusedVh == null) {
+            resetFocusInfo();
+        } else {
+            mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID;
+            mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION :
+                    focusedVh.getAdapterPosition();
+            mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView);
+        }
+    }
+
+    private void resetFocusInfo() {
+        mState.mFocusedItemId = NO_ID;
+        mState.mFocusedItemPosition = NO_POSITION;
+        mState.mFocusedSubChildId = View.NO_ID;
+    }
+
+    private void recoverFocusFromState() {
+        if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus()) {
+            return;
+        }
+        // only recover focus if RV itself has the focus or the focused view is hidden
+        if (!isFocused()) {
+            final View focusedChild = getFocusedChild();
+            if (focusedChild == null || !mChildHelper.isHidden(focusedChild)) {
+                return;
+            }
+        }
+        ViewHolder focusTarget = null;
+        if (mState.mFocusedItemPosition != NO_POSITION) {
+            focusTarget = findViewHolderForAdapterPosition(mState.mFocusedItemPosition);
+        }
+        if (focusTarget == null && mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) {
+            focusTarget = findViewHolderForItemId(mState.mFocusedItemId);
+        }
+        if (focusTarget == null || focusTarget.itemView.hasFocus() ||
+                !focusTarget.itemView.hasFocusable()) {
+            return;
+        }
+        // looks like the focused item has been replaced with another view that represents the
+        // same item in the adapter. Request focus on that.
+        View viewToFocus = focusTarget.itemView;
+        if (mState.mFocusedSubChildId != NO_ID) {
+            View child = focusTarget.itemView.findViewById(mState.mFocusedSubChildId);
+            if (child != null && child.isFocusable()) {
+                viewToFocus = child;
+            }
+        }
+        viewToFocus.requestFocus();
+    }
+
+    private int getDeepestFocusedViewWithId(View view) {
+        int lastKnownId = view.getId();
+        while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) {
+            view = ((ViewGroup) view).getFocusedChild();
+            final int id = view.getId();
+            if (id != View.NO_ID) {
+                lastKnownId = view.getId();
+            }
+        }
+        return lastKnownId;
+    }
+
     /**
      * The first step of a layout where we;
      * - process adapter updates
@@ -2930,7 +2998,7 @@
         eatRequestLayout();
         mViewInfoStore.clear();
         onEnterLayoutOrScroll();
-
+        saveFocusInfo();
         processAdapterUpdatesAndSetAnimationFlags();
         mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
         mItemsAddedOrRemoved = mItemsChanged = false;
@@ -3108,12 +3176,15 @@
         if (mRecycler.mChangedScrap != null) {
             mRecycler.mChangedScrap.clear();
         }
+        mLayout.onLayoutCompleted(mState);
         onExitLayoutOrScroll();
         resumeRequestLayout(false);
         mViewInfoStore.clear();
         if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
             dispatchOnScrolled(0, 0);
         }
+        recoverFocusFromState();
+        resetFocusInfo();
     }
 
     /**
@@ -3602,6 +3673,39 @@
     }
 
     /**
+     * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's
+     * focus even if the View representing the Item is replaced during a layout calculation.
+     * <p>
+     * By default, this value is {@code true}.
+     *
+     * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses
+     * focus.
+     *
+     * @see #setPreserveFocusAfterLayout(boolean)
+     */
+    public boolean getPreserveFocusAfterLayout() {
+        return mPreserveFocusAfterLayout;
+    }
+
+    /**
+     * Set whether the RecyclerView should try to keep the same Item focused after a layout
+     * calculation or not.
+     * <p>
+     * Usually, LayoutManagers keep focused views visible before and after layout but sometimes,
+     * views may lose focus during a layout calculation as their state changes or they are replaced
+     * with another view due to type change or animation. In these cases, RecyclerView can request
+     * focus on the new view automatically.
+     *
+     * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a
+     *                                 layout calculations. Defaults to true.
+     *
+     * @see #getPreserveFocusAfterLayout()
+     */
+    public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) {
+        mPreserveFocusAfterLayout = preserveFocusAfterLayout;
+    }
+
+    /**
      * Retrieve the {@link ViewHolder} for the given child view.
      *
      * @param child Child of this RecyclerView to query for its ViewHolder
@@ -3730,6 +3834,10 @@
      * next layout calculation. If there are pending adapter updates, the return value of this
      * method may not match your adapter contents. You can use
      * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder.
+     * <p>
+     * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
+     * with the same layout position representing the same Item. In this case, the updated
+     * ViewHolder will be returned.
      *
      * @param position The position of the item in the data set of the adapter
      * @return The ViewHolder at <code>position</code> or null if there is no such item
@@ -3748,6 +3856,9 @@
      * <p>
      * This method checks only the children of RecyclerView. If the item at the given
      * <code>position</code> is not laid out, it <em>will not</em> create a new one.
+     * <p>
+     * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
+     * representing the same Item. In this case, the updated ViewHolder will be returned.
      *
      * @param position The position of the item in the data set of the adapter
      * @return The ViewHolder at <code>position</code> or null if there is no such item
@@ -3757,25 +3868,37 @@
             return null;
         }
         final int childCount = mChildHelper.getUnfilteredChildCount();
+        // hidden VHs are not preferred but if that is the only one we find, we rather return it
+        ViewHolder hidden = null;
         for (int i = 0; i < childCount; i++) {
             final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
             if (holder != null && !holder.isRemoved() && getAdapterPositionFor(holder) == position) {
-                return holder;
+                if (mChildHelper.isHidden(holder.itemView)) {
+                    hidden = holder;
+                } else {
+                    return holder;
+                }
             }
         }
-        return null;
+        return hidden;
     }
 
     ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
         final int childCount = mChildHelper.getUnfilteredChildCount();
+        ViewHolder hidden = null;
         for (int i = 0; i < childCount; i++) {
             final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
             if (holder != null && !holder.isRemoved()) {
                 if (checkNewPosition) {
-                    if (holder.mPosition == position) {
-                        return holder;
+                    if (holder.mPosition != position) {
+                        continue;
                     }
-                } else if (holder.getLayoutPosition() == position) {
+                } else if (holder.getLayoutPosition() != position) {
+                    continue;
+                }
+                if (mChildHelper.isHidden(holder.itemView)) {
+                    hidden = holder;
+                } else {
                     return holder;
                 }
             }
@@ -3783,7 +3906,7 @@
         // This method should not query cached views. It creates a problem during adapter updates
         // when we are dealing with already laid out views. Also, for the public method, it is more
         // reasonable to return null if position is not laid out.
-        return null;
+        return hidden;
     }
 
     /**
@@ -3794,20 +3917,29 @@
      * This method checks only the children of RecyclerView. If the item with the given
      * <code>id</code> is not laid out, it <em>will not</em> create a new one.
      *
+     * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the
+     * same id. In this case, the updated ViewHolder will be returned.
+     *
      * @param id The id for the requested item
      * @return The ViewHolder with the given <code>id</code> or null if there is no such item
      */
     public ViewHolder findViewHolderForItemId(long id) {
+        if (mAdapter == null || !mAdapter.hasStableIds()) {
+            return null;
+        }
         final int childCount = mChildHelper.getUnfilteredChildCount();
+        ViewHolder hidden = null;
         for (int i = 0; i < childCount; i++) {
             final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
-            if (holder != null && holder.getItemId() == id) {
-                return holder;
+            if (holder != null && !holder.isRemoved() && holder.getItemId() == id) {
+                if (mChildHelper.isHidden(holder.itemView)) {
+                    hidden = holder;
+                } else {
+                    return holder;
+                }
             }
         }
-        // this method should not query cached views. They are not children so they
-        // should not be returned in this public method
-        return null;
+        return hidden;
     }
 
     /**
@@ -6429,6 +6561,20 @@
         }
 
         /**
+         * Called after a full layout calculation is finished. The layout calculation may include
+         * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or
+         * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call.
+         * This method will be called at the end of {@link View#layout(int, int, int, int)} call.
+         * <p>
+         * This is a good place for the LayoutManager to do some cleanup like pending scroll
+         * position, saved state etc.
+         *
+         * @param state Transient state of RecyclerView
+         */
+        public void onLayoutCompleted(State state) {
+        }
+
+        /**
          * Create a default <code>LayoutParams</code> object for a child of the RecyclerView.
          *
          * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type
@@ -7063,11 +7209,11 @@
          * {@link #setAutoMeasureEnabled(boolean)}.
          * <p>
          * When RecyclerView is running a layout, this value is always set to
-         * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+         * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
          *
          * @return Width measure spec mode.
          *
-         * @see MeasureSpec#getMode(int)
+         * @see View.MeasureSpec#getMode(int)
          * @see View#onMeasure(int, int)
          */
         public int getWidthMode() {
@@ -7081,11 +7227,11 @@
          * {@link #setAutoMeasureEnabled(boolean)}.
          * <p>
          * When RecyclerView is running a layout, this value is always set to
-         * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+         * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
          *
          * @return Height measure spec mode.
          *
-         * @see MeasureSpec#getMode(int)
+         * @see View.MeasureSpec#getMode(int)
          * @see View#onMeasure(int, int)
          */
         public int getHeightMode() {
@@ -7691,6 +7837,43 @@
         }
 
         /**
+         * Calculates the bounding box of the View while taking into account its matrix changes
+         * (translation, scale etc) wrt to the RecyclerView.
+         * <p>
+         * If {@code includeDecorInsets} is {@code true}, they are applied first before applying
+         * the View's matrix so that the decor offsets also go through the same transformation.
+         *
+         * @param child The ItemView whose bounding box should be calculated.
+         * @param includeDecorInsets True if the decor insets should be included in the bounding box
+         * @param out The rectangle into which the output will be written.
+         */
+        public void getTransformedBoundingBox(View child, boolean includeDecorInsets, Rect out) {
+            if (includeDecorInsets) {
+                Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+                out.set(-insets.left, -insets.top,
+                        child.getWidth() + insets.right, child.getHeight() + insets.bottom);
+            } else {
+                out.set(0, 0, child.getWidth(), child.getHeight());
+            }
+
+            if (mRecyclerView != null) {
+                final Matrix childMatrix = ViewCompat.getMatrix(child);
+                if (childMatrix != null && !childMatrix.isIdentity()) {
+                    final RectF tempRectF = mRecyclerView.mTempRectF;
+                    tempRectF.set(out);
+                    childMatrix.mapRect(tempRectF);
+                    out.set(
+                            (int) Math.floor(tempRectF.left),
+                            (int) Math.floor(tempRectF.top),
+                            (int) Math.ceil(tempRectF.right),
+                            (int) Math.ceil(tempRectF.bottom)
+                    );
+                }
+            }
+            out.offset(child.getLeft(), child.getTop());
+        }
+
+        /**
          * Returns the bounds of the view including its decoration and margins.
          *
          * @param view The view element to check
@@ -10152,6 +10335,17 @@
 
         private boolean mIsMeasuring = false;
 
+        /**
+         * This data is saved before a layout calculation happens. After the layout is finished,
+         * if the previously focused view has been replaced with another view for the same item, we
+         * move the focus to the new item automatically.
+         */
+        int mFocusedItemPosition;
+        long mFocusedItemId;
+        // when a sub child has focus, record its id and see if we can directly request focus on
+        // that one instead
+        int mFocusedSubChildId;
+
         State reset() {
             mTargetPosition = RecyclerView.NO_POSITION;
             if (mData != null) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index 2542031..18987ad 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -614,7 +614,7 @@
 
         updateAnchorInfoForLayout(state, anchorInfo);
 
-        if (mPendingSavedState == null) {
+        if (mPendingSavedState == null && mPendingScrollPosition == NO_POSITION) {
             if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd ||
                     isLayoutRTL() != mLastLayoutRTL) {
                 mLazySpanLookup.clear();
@@ -683,17 +683,22 @@
                     hasGaps = true;
                 }
             }
-            mPendingScrollPosition = NO_POSITION;
-            mPendingScrollPositionOffset = INVALID_OFFSET;
         }
         mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
         mLastLayoutRTL = isLayoutRTL();
-        mPendingSavedState = null; // we don't need this anymore
         if (hasGaps) {
             onLayoutChildren(recycler, state, false);
         }
     }
 
+    @Override
+    public void onLayoutCompleted(RecyclerView.State state) {
+        super.onLayoutCompleted(state);
+        mPendingScrollPosition = NO_POSITION;
+        mPendingScrollPositionOffset = INVALID_OFFSET;
+        mPendingSavedState = null; // we don't need this anymore
+    }
+
     private void repositionToWrapContentIfNecessary() {
         if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) {
             return; // nothing to do
@@ -829,7 +834,6 @@
                 // child
                 anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
                         : getFirstChildPosition();
-
                 if (mPendingScrollPositionOffset != INVALID_OFFSET) {
                     if (anchorInfo.mLayoutFromEnd) {
                         final int target = mPrimaryOrientation.getEndAfterPadding() -
@@ -1837,7 +1841,8 @@
     private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
         while (getChildCount() > 0) {
             View child = getChildAt(0);
-            if (mPrimaryOrientation.getDecoratedEnd(child) <= line) {
+            if (mPrimaryOrientation.getDecoratedEnd(child) <= line &&
+                    mPrimaryOrientation.getTransformedEndWithDecoration(child) <= line) {
                 LayoutParams lp = (LayoutParams) child.getLayoutParams();
                 // Don't recycle the last View in a span not to lose span's start/end lines
                 if (lp.mFullSpan) {
@@ -1867,7 +1872,8 @@
         int i;
         for (i = childCount - 1; i >= 0; i--) {
             View child = getChildAt(i);
-            if (mPrimaryOrientation.getDecoratedStart(child) >= line) {
+            if (mPrimaryOrientation.getDecoratedStart(child) >= line &&
+                    mPrimaryOrientation.getTransformedStartWithDecoration(child) >= line) {
                 LayoutParams lp = (LayoutParams) child.getLayoutParams();
                 // Don't recycle the last View in a span not to lose span's start/end lines
                 if (lp.mFullSpan) {
diff --git a/v7/recyclerview/tests/res/layout/focus_test_item_view.xml b/v7/recyclerview/tests/res/layout/focus_test_item_view.xml
new file mode 100644
index 0000000..8f81872
--- /dev/null
+++ b/v7/recyclerview/tests/res/layout/focus_test_item_view.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:id="@+id/item_root"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <FrameLayout android:layout_width="wrap_content"
+                 android:id="@+id/parent2"
+                 android:layout_height="wrap_content">
+        <FrameLayout android:layout_width="wrap_content"
+                     android:id="@+id/parent1"
+                     android:layout_height="wrap_content">
+            <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+            android:id="@+id/text_view"/>
+        </FrameLayout>
+    </FrameLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
index af2cea7..8bd003d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
@@ -16,6 +16,8 @@
 
 package android.support.v7.widget;
 
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -32,6 +34,8 @@
 import java.util.concurrent.TimeUnit;
 import static org.junit.Assert.*;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class AsyncListUtilLayoutTest extends BaseRecyclerViewInstrumentationTest {
@@ -313,11 +317,17 @@
             mLayoutLatch = new CountDownLatch(count);
         }
 
-        public void waitForLayout(long timeout) throws InterruptedException {
-            mLayoutLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
-            assertEquals("all expected layouts should be executed at the expected time",
-                    0, mLayoutLatch.getCount());
-            getInstrumentation().waitForIdleSync();
+        public void waitForLayout(int seconds) throws Throwable {
+            mLayoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
+            checkForMainThreadException();
+            MatcherAssert.assertThat("all layouts should complete on time",
+                    mLayoutLatch.getCount(), CoreMatchers.is(0L));
+            // use a runnable to ensure RV layout is finished
+            getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                }
+            });
         }
 
         @Override
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/AttachDetachCollector.java b/v7/recyclerview/tests/src/android/support/v7/widget/AttachDetachCollector.java
new file mode 100644
index 0000000..12281ae
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/AttachDetachCollector.java
@@ -0,0 +1,57 @@
+/*
+ * 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.widget;
+
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple class that can collect list of view attach and detach events so that we can assert on them
+ */
+public class AttachDetachCollector implements RecyclerView.OnChildAttachStateChangeListener {
+    private final List<View> mAttached = new ArrayList<>();
+    private final List<View> mDetached = new ArrayList<>();
+
+    public AttachDetachCollector(RecyclerView recyclerView) {
+        recyclerView.addOnChildAttachStateChangeListener(this);
+    }
+
+    @Override
+    public void onChildViewAttachedToWindow(View view) {
+        mAttached.add(view);
+    }
+
+    @Override
+    public void onChildViewDetachedFromWindow(View view) {
+        mDetached.add(view);
+    }
+
+    public void reset() {
+        mAttached.clear();
+        mDetached.clear();
+    }
+
+    public List<View> getAttached() {
+        return mAttached;
+    }
+
+    public List<View> getDetached() {
+        return mDetached;
+    }
+}
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 cfe6fe1..aa1be7b 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -29,6 +29,9 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.junit.Assert.assertEquals;
 
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+
 public class BaseGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
 
     static final String TAG = "GridLayoutManagerTest";
@@ -194,8 +197,17 @@
             mLayoutLatch = new CountDownLatch(layoutCount);
         }
 
-        public void waitForLayout(int seconds) throws InterruptedException {
-            mLayoutLatch.await(seconds, SECONDS);
+        public void waitForLayout(int seconds) throws Throwable {
+            mLayoutLatch.await(seconds * (DEBUG ? 1000 : 1), SECONDS);
+            checkForMainThreadException();
+            MatcherAssert.assertThat("all layouts should complete on time",
+                    mLayoutLatch.getCount(), CoreMatchers.is(0L));
+            // use a runnable to ensure RV layout is finished
+            getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                }
+            });
         }
     }
 
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 4e37369..305fa66 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
@@ -35,6 +35,11 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static org.junit.Assert.*;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+
 public class BaseLinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
 
     protected static final boolean DEBUG = false;
@@ -347,8 +352,17 @@
             layoutLatch = new CountDownLatch(count);
         }
 
-        public void waitForLayout(long timeout) throws InterruptedException {
-            waitForLayout(timeout, TimeUnit.SECONDS);
+        public void waitForLayout(int seconds) throws Throwable {
+            layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
+            checkForMainThreadException();
+            MatcherAssert.assertThat("all layouts should complete on time",
+                    layoutLatch.getCount(), CoreMatchers.is(0L));
+            // use a runnable to ensure RV layout is finished
+            getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                }
+            });
         }
 
         @Override
@@ -383,13 +397,6 @@
             }
         }
 
-        private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
-            layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit);
-            assertEquals("all expected layouts should be executed at the expected time",
-                    0, layoutLatch.getCount());
-            getInstrumentation().waitForIdleSync();
-        }
-
         @Override
         LayoutState createLayoutState() {
             return new LayoutState() {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
index 25876a7..57c09fa 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
@@ -207,12 +207,6 @@
         public void onPostDispatchLayout() {
             mOnLayoutCallbacks.postDispatchLayout();
         }
-
-        @Override
-        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
-            super.waitForLayout(timeout, timeUnit);
-            checkForMainThreadException();
-        }
     }
 
     abstract class OnLayoutCallbacks {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index ca9fddf..3f02ebe 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -24,17 +24,13 @@
 import org.junit.Before;
 import org.junit.Rule;
 
-import android.app.Activity;
 import android.app.Instrumentation;
-import android.content.Intent;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.recyclerview.test.SameActivityTestRule;
-import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -57,6 +53,8 @@
 
 import static org.junit.Assert.*;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
 abstract public class BaseRecyclerViewInstrumentationTest {
 
     private static final String TAG = "RecyclerViewTest";
@@ -67,7 +65,9 @@
 
     protected AdapterHelper mAdapterHelper;
 
-    Throwable mainThreadException;
+    private Throwable mMainThreadException;
+
+    private boolean mIgnoreMainThreadException = false;
 
     Thread mInstrumentationThread;
 
@@ -88,11 +88,19 @@
     }
 
     void checkForMainThreadException() throws Throwable {
-        if (mainThreadException != null) {
-            throw mainThreadException;
+        if (!mIgnoreMainThreadException && mMainThreadException != null) {
+            throw mMainThreadException;
         }
     }
 
+    public void setIgnoreMainThreadException(boolean ignoreMainThreadException) {
+        mIgnoreMainThreadException = ignoreMainThreadException;
+    }
+
+    public Throwable getMainThreadException() {
+        return mMainThreadException;
+    }
+
     protected TestActivity getActivity() {
         return mActivityRule.getActivity();
     }
@@ -172,19 +180,21 @@
         if (mInstrumentationThread == Thread.currentThread()) {
             throw new RuntimeException(t);
         }
-        if (mainThreadException != null) {
+        if (mMainThreadException != null) {
             Log.e(TAG, "receiving another main thread exception. dropping.", t);
         } else {
             Log.e(TAG, "captured exception on main thread", t);
-            mainThreadException = t;
+            mMainThreadException = t;
         }
 
         if (mRecyclerView != null && mRecyclerView
                 .getLayoutManager() instanceof TestLayoutManager) {
             TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
             // finish all layouts so that we get the correct exception
-            while (lm.layoutLatch.getCount() > 0) {
-                lm.layoutLatch.countDown();
+            if (lm.layoutLatch != null) {
+                while (lm.layoutLatch.getCount() > 0) {
+                    lm.layoutLatch.countDown();
+                }
             }
         }
     }
@@ -233,10 +243,13 @@
             @Override
             public void run() {
                 try {
-                    final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
-                    if (adapter instanceof AttachDetachCountingAdapter) {
-                        ((AttachDetachCountingAdapter) adapter).getCounter()
-                                .validateRemaining(mRecyclerView);
+                    // do not run validation if we already have an error
+                    if (mMainThreadException == null) {
+                        final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
+                        if (adapter instanceof AttachDetachCountingAdapter) {
+                            ((AttachDetachCountingAdapter) adapter).getCounter()
+                                    .validateRemaining(mRecyclerView);
+                        }
                     }
                     getActivity().mContainer.removeAllViews();
                 } catch (Throwable t) {
@@ -503,47 +516,49 @@
         }
     }
     class DumbLayoutManager extends TestLayoutManager {
-        ReentrantLock mLayoutLock = new ReentrantLock();
-        public void blockLayout() {
-            mLayoutLock.lock();
-        }
-
-        public void unblockLayout() {
-            mLayoutLock.unlock();
-        }
         @Override
         public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
-            mLayoutLock.lock();
             detachAndScrapAttachedViews(recycler);
             layoutRange(recycler, 0, state.getItemCount());
             if (layoutLatch != null) {
                 layoutLatch.countDown();
             }
-            mLayoutLock.unlock();
         }
     }
     public class TestLayoutManager extends RecyclerView.LayoutManager {
         int mScrollVerticallyAmount;
         int mScrollHorizontallyAmount;
         protected CountDownLatch layoutLatch;
+        private boolean mSupportsPredictive = false;
 
         public void expectLayouts(int count) {
             layoutLatch = new CountDownLatch(count);
         }
 
-        public void waitForLayout(long timeout, TimeUnit timeUnit, boolean waitForIdle)
-                throws Throwable {
-            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
-            assertEquals("all expected layouts should be executed at the expected time",
-                    0, layoutLatch.getCount());
-            if (waitForIdle) {
-                getInstrumentation().waitForIdleSync();
-            }
+        public void waitForLayout(int seconds) throws Throwable {
+            layoutLatch.await(seconds * (mDebug ? 1000 : 1), SECONDS);
+            checkForMainThreadException();
+            MatcherAssert.assertThat("all layouts should complete on time",
+                    layoutLatch.getCount(), CoreMatchers.is(0L));
+            // use a runnable to ensure RV layout is finished
+            getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                }
+            });
         }
 
-        public void waitForLayout(long timeout, TimeUnit timeUnit)
-                throws Throwable {
-            waitForLayout(timeout, timeUnit, true);
+        public boolean isSupportsPredictive() {
+            return mSupportsPredictive;
+        }
+
+        public void setSupportsPredictive(boolean supportsPredictive) {
+            mSupportsPredictive = supportsPredictive;
+        }
+
+        @Override
+        public boolean supportsPredictiveItemAnimations() {
+            return mSupportsPredictive;
         }
 
         public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
@@ -556,14 +571,6 @@
             assertFalse(msg, layoutLatch.getCount() == 0);
         }
 
-        public void waitForLayout(long timeout) throws Throwable {
-            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS, true);
-        }
-
-        public void waitForLayout(long timeout, boolean waitForIdle) throws Throwable {
-            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS, waitForIdle);
-        }
-
         @Override
         public RecyclerView.LayoutParams generateDefaultLayoutParams() {
             return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
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 b146355..0fea8d6 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
@@ -28,6 +28,11 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+
 public class BaseStaggeredGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
 
     protected static final boolean DEBUG = false;
@@ -108,7 +113,7 @@
     void waitFirstLayout() throws Throwable {
         mLayoutManager.expectLayouts(1);
         setRecyclerView(mRecyclerView);
-        mLayoutManager.waitForLayout(2);
+        mLayoutManager.waitForLayout(3);
         getInstrumentation().waitForIdleSync();
     }
 
@@ -480,15 +485,17 @@
             layoutLatch = new CountDownLatch(count);
         }
 
-        public void waitForLayout(long timeout) throws Throwable {
-            waitForLayout(timeout * (DEBUG ? 1000 : 1), TimeUnit.SECONDS);
-        }
-
-        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
-            layoutLatch.await(timeout, timeUnit);
+        public void waitForLayout(int seconds) throws Throwable {
+            layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
             checkForMainThreadException();
-            assertEquals("all expected layouts should be executed at the expected time",
-                    0, layoutLatch.getCount());
+            MatcherAssert.assertThat("all layouts should complete on time",
+                    layoutLatch.getCount(), CoreMatchers.is(0L));
+            // use a runnable to ensure RV layout is finished
+            getInstrumentation().runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                }
+            });
         }
 
         public void assertNoLayout(String msg, long timeout) throws Throwable {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index 4a15e48..b38c551 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -775,7 +775,6 @@
                 assertFalse(mGlm.supportsPredictiveItemAnimations());
             }
         });
-        checkForMainThreadException();
         mGlm.waitForLayout(2);
         mGlm.expectLayout(2);
         mAdapter.deleteAndNotify(3, 2);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
index 73e913c..9ee7958 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
@@ -22,10 +22,12 @@
 import org.junit.runners.Parameterized;
 
 import android.graphics.Rect;
+import android.support.v4.view.ViewCompat;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -35,8 +37,15 @@
 import static android.support.v7.widget.LayoutState.LAYOUT_START;
 import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
 
@@ -44,6 +53,7 @@
  * Tests that rely on the basic configuration and does not do any additions / removals
  */
 @RunWith(Parameterized.class)
+@MediumTest
 public class LinearLayoutManagerBaseConfigSetTest extends BaseLinearLayoutManagerTest {
 
     private final Config mConfig;
@@ -63,7 +73,6 @@
     }
 
     @Test
-    @MediumTest
     public void scrollToPositionWithOffsetTest() throws Throwable {
         Config config = ((Config) mConfig.clone()).itemCount(300);
         setupByConfig(config, true);
@@ -148,7 +157,6 @@
     }
 
     @Test
-    @MediumTest
     public void getFirstLastChildrenTest() throws Throwable {
         final Config config = ((Config) mConfig.clone()).itemCount(300);
         setupByConfig(config, true);
@@ -224,6 +232,73 @@
         runTestOnUiThread(viewInBoundsTest);
     }
 
+    @Test
+    public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable {
+        final Config config = ((Config) mConfig.clone()).itemCount(1000);
+        setupByConfig(config, true);
+        mLayoutManager.expectLayouts(1);
+        scrollToPosition(500);
+        mLayoutManager.waitForLayout(2);
+        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500);
+        OrientationHelper helper = mLayoutManager.mOrientationHelper;
+        int gap = helper.getDecoratedStart(vh.itemView);
+        scrollBy(gap);
+        gap = helper.getDecoratedStart(vh.itemView);
+        assertThat("test sanity", gap, is(0));
+
+        final int size = helper.getDecoratedMeasurement(vh.itemView);
+        AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (mConfig.mOrientation == HORIZONTAL) {
+                    ViewCompat.setTranslationX(vh.itemView, size * 2);
+                } else {
+                    ViewCompat.setTranslationY(vh.itemView, size * 2);
+                }
+            }
+        });
+        scrollBy(size * 2);
+        assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
+        assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
+        assertThat(vh.getAdapterPosition(), is(500));
+        scrollBy(size * 2);
+        assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
+    }
+
+    @Test
+    public void dontRecycleViewsTranslatedOutOfBoundsFromEnd() throws Throwable {
+        final Config config = ((Config) mConfig.clone()).itemCount(1000);
+        setupByConfig(config, true);
+        mLayoutManager.expectLayouts(1);
+        scrollToPosition(500);
+        mLayoutManager.waitForLayout(2);
+        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500);
+        OrientationHelper helper = mLayoutManager.mOrientationHelper;
+        int gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
+        scrollBy(-gap);
+        gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
+        assertThat("test sanity", gap, is(0));
+
+        final int size = helper.getDecoratedMeasurement(vh.itemView);
+        AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (mConfig.mOrientation == HORIZONTAL) {
+                    ViewCompat.setTranslationX(vh.itemView, -size * 2);
+                } else {
+                    ViewCompat.setTranslationY(vh.itemView, -size * 2);
+                }
+            }
+        });
+        scrollBy(-size * 2);
+        assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
+        assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
+        assertThat(vh.getAdapterPosition(), is(500));
+        scrollBy(-size * 2);
+        assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
+    }
 
     private TargetTuple findInvisibleTarget(Config config) {
         int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
index 3b9f5d6..be0fc10 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
@@ -261,14 +261,20 @@
         });
         assertTrue("test sanity", info.isScrollable());
         final AccessibilityNodeInfoCompat info2 = AccessibilityNodeInfoCompat.obtain();
-        layoutManager.blockLayout();
-        layoutManager.expectLayouts(1);
-        adapter.deleteAndNotify(1, 1);
-        // we can run this here since we blocked layout.
-        delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info2);
-        layoutManager.unblockLayout();
-        assertFalse("info should not be filled if data is out of date", info2.isScrollable());
-        layoutManager.waitForLayout(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    adapter.deleteAndNotify(1, 1);
+                } catch (Throwable throwable) {
+                    postExceptionToInstrumentation(throwable);
+                }
+                delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info2);
+                assertFalse("info should not be filled if data is out of date",
+                        info2.isScrollable());
+            }
+        });
+        checkForMainThreadException();
     }
 
     boolean performAccessibilityAction(final AccessibilityDelegateCompat delegate,
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
index 2b8fb40..2ddbabf 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -23,8 +23,6 @@
 
 import android.graphics.Rect;
 import android.support.annotation.NonNull;
-import android.os.Debug;
-import android.support.annotation.NonNull;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.view.ViewCompat;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -52,6 +50,38 @@
     final List<TestViewHolder> recycledVHs = new ArrayList<>();
 
     @Test
+    public void keepFocusAfterChangeAnimation() throws Throwable {
+        setupBasic(10, 0, 5, new TestAdapter(10) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                holder.itemView.setFocusableInTouchMode(true);
+            }
+        });
+        ((SimpleItemAnimator)(mRecyclerView.getItemAnimator())).setSupportsChangeAnimations(true);
+
+        final RecyclerView.ViewHolder oldVh = mRecyclerView.findViewHolderForAdapterPosition(3);
+        assertNotNull("test sanity", oldVh);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                oldVh.itemView.requestFocus();
+            }
+        });
+        assertTrue("test sanity", oldVh.itemView.hasFocus());
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.changeAndNotify(3, 1);
+        mLayoutManager.waitForLayout(2);
+
+        RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(3);
+        assertNotNull("test sanity", newVh);
+        assertNotSame(oldVh, newVh);
+        assertFalse(oldVh.itemView.hasFocus());
+        assertTrue(newVh.itemView.hasFocus());
+    }
+
+    @Test
     public void changeAndDisappearDontReUseViewHolder() throws Throwable {
         changeAndDisappearTest(false, false);
     }
@@ -141,6 +171,7 @@
 
     @Test
     public void detectStableIdError() throws Throwable {
+        setIgnoreMainThreadException(true);
         final AtomicBoolean useBadIds = new AtomicBoolean(false);
         TestAdapter adapter = new TestAdapter(10) {
             @Override
@@ -162,14 +193,13 @@
         useBadIds.set(true);
         adapter.changeAndNotify(4, 2);
         mLayoutManager.waitForLayout(2);
-        assertTrue(mainThreadException instanceof IllegalStateException);
-        assertTrue(mainThreadException.getMessage()
+        assertTrue(getMainThreadException() instanceof IllegalStateException);
+        assertTrue(getMainThreadException().getMessage()
                 .contains("Two different ViewHolders have the same stable ID."));
         // TODO don't use this after moving this class to Junit 4
         try {
             removeRecyclerView();
         } catch (Throwable t){}
-        mainThreadException = null;
     }
 
 
@@ -1368,7 +1398,7 @@
         // add 2 items
         setExpectedItemCounts(1, 3);
         mTestAdapter.addAndNotify(0, 2);
-        mLayoutManager.waitForLayout(2, false);
+        mLayoutManager.waitForLayout(2);
         checkForMainThreadException();
         // wait till "add animation" starts
         int limit = 200;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
new file mode 100644
index 0000000..ea1573d
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
@@ -0,0 +1,553 @@
+/*
+ * 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.widget;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.recyclerview.test.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This class only tests the RV's focus recovery logic as focus moves between two views that
+ * represent the same item in the adapter. Keeping a focused view visible is up-to-the
+ * LayoutManager and all FW LayoutManagers already have tests for it.
+ */
+@RunWith(Parameterized.class)
+public class RecyclerViewFocusRecoveryTest extends BaseRecyclerViewInstrumentationTest {
+    TestLayoutManager mLayoutManager;
+    TestAdapter mAdapter;
+
+    private final boolean mFocusOnChild;
+    private final boolean mDisableRecovery;
+
+    @Parameterized.Parameters(name = "focusSubChild:{0}, disable:{1}")
+    public static List<Object[]> getParams() {
+        return Arrays.asList(
+                new Object[]{false, false},
+                new Object[]{true, false},
+                new Object[]{false, true},
+                new Object[]{true, true}
+        );
+    }
+
+    public RecyclerViewFocusRecoveryTest(boolean focusOnChild, boolean disableRecovery) {
+        super(false);
+        mFocusOnChild = focusOnChild;
+        mDisableRecovery = disableRecovery;
+    }
+
+    void setupBasic() throws Throwable {
+        setupBasic(false);
+    }
+
+    void setupBasic(boolean hasStableIds) throws Throwable {
+        TestAdapter adapter = new FocusTestAdapter(10);
+        adapter.setHasStableIds(hasStableIds);
+        setupBasic(adapter, null);
+    }
+
+    void setupBasic(TestLayoutManager layoutManager) throws Throwable {
+        setupBasic(null, layoutManager);
+    }
+
+    void setupBasic(TestAdapter adapter) throws Throwable {
+        setupBasic(adapter, null);
+    }
+
+    void setupBasic(@Nullable TestAdapter adapter, @Nullable TestLayoutManager layoutManager)
+            throws Throwable {
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        if (layoutManager == null) {
+            layoutManager = new FocusLayoutManager();
+        }
+
+        if (adapter == null) {
+            adapter = new FocusTestAdapter(10);
+        }
+        mLayoutManager = layoutManager;
+        mAdapter = adapter;
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(mLayoutManager);
+        recyclerView.setPreserveFocusAfterLayout(!mDisableRecovery);
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        mLayoutManager.waitForLayout(1);
+    }
+
+    @Test
+    public void testFocusRecoveryInChange() throws Throwable {
+        setupBasic();
+        ((SimpleItemAnimator) (mRecyclerView.getItemAnimator())).setSupportsChangeAnimations(true);
+        mLayoutManager.setSupportsPredictive(true);
+        final RecyclerView.ViewHolder oldVh = focusVh(3);
+
+        mLayoutManager.expectLayouts(2);
+        mAdapter.changeAndNotify(3, 1);
+        mLayoutManager.waitForLayout(2);
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(3);
+                assertFocusTransition(oldVh, newVh);
+
+            }
+        });
+        mLayoutManager.expectLayouts(1);
+    }
+
+    private void assertFocusTransition(RecyclerView.ViewHolder oldVh,
+            RecyclerView.ViewHolder newVh) {
+        if (mDisableRecovery) {
+            assertFocus(newVh, false);
+            return;
+        }
+        assertThat("test sanity", newVh, notNullValue());
+        assertThat(oldVh, not(sameInstance(newVh)));
+        assertFocus(oldVh, false);
+        assertFocus(newVh, true);
+    }
+
+    @Test
+    public void testFocusRecoveryInTypeChangeWithPredictive() throws Throwable {
+        testFocusRecoveryInTypeChange(true);
+    }
+
+    @Test
+    public void testFocusRecoveryInTypeChangeWithoutPredictive() throws Throwable {
+        testFocusRecoveryInTypeChange(false);
+    }
+
+    private void testFocusRecoveryInTypeChange(boolean withAnimation) throws Throwable {
+        setupBasic();
+        ((SimpleItemAnimator) (mRecyclerView.getItemAnimator())).setSupportsChangeAnimations(true);
+        mLayoutManager.setSupportsPredictive(withAnimation);
+        final RecyclerView.ViewHolder oldVh = focusVh(3);
+        mLayoutManager.expectLayouts(withAnimation ? 2 : 1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Item item = mAdapter.mItems.get(3);
+                item.mType += 2;
+                mAdapter.notifyItemChanged(3);
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+
+        RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(3);
+        assertFocusTransition(oldVh, newVh);
+        assertThat("test sanity", oldVh.getItemViewType(), not(newVh.getItemViewType()));
+    }
+
+    @Test
+    public void testRecoverAdapterChangeViaStableIdOnDataSetChanged() throws Throwable {
+        recoverAdapterChangeViaStableId(false, false);
+    }
+
+    @Test
+    public void testRecoverAdapterChangeViaStableIdOnSwap() throws Throwable {
+        recoverAdapterChangeViaStableId(true, false);
+    }
+
+    @Test
+    public void testRecoverAdapterChangeViaStableIdOnDataSetChangedWithTypeChange()
+            throws Throwable {
+        recoverAdapterChangeViaStableId(false, true);
+    }
+
+    @Test
+    public void testRecoverAdapterChangeViaStableIdOnSwapWithTypeChange() throws Throwable {
+        recoverAdapterChangeViaStableId(true, true);
+    }
+
+    private void recoverAdapterChangeViaStableId(final boolean swap, final boolean changeType)
+            throws Throwable {
+        setupBasic(true);
+        RecyclerView.ViewHolder oldVh = focusVh(4);
+        long itemId = oldVh.getItemId();
+
+        mLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Item item = mAdapter.mItems.get(4);
+                if (changeType) {
+                    item.mType += 2;
+                }
+                if (swap) {
+                    mAdapter = new FocusTestAdapter(8);
+                    mAdapter.setHasStableIds(true);
+                    mAdapter.mItems.add(2, item);
+                    mRecyclerView.swapAdapter(mAdapter, false);
+                } else {
+                    mAdapter.mItems.remove(0);
+                    mAdapter.mItems.remove(0);
+                    mAdapter.notifyDataSetChanged();
+                }
+            }
+        });
+        mLayoutManager.waitForLayout(1);
+
+        RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForItemId(itemId);
+        if (changeType) {
+            assertFocusTransition(oldVh, newVh);
+        } else {
+            // in this case we should use the same VH because we have stable ids
+            assertThat(oldVh, sameInstance(newVh));
+            assertFocus(newVh, true);
+        }
+    }
+
+    @Test
+    public void testDoNotRecoverViaPositionOnSetAdapter() throws Throwable {
+        testDoNotRecoverViaPositionOnNewDataSet(new RecyclerViewLayoutTest.AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                mRecyclerView.setAdapter(new FocusTestAdapter(10));
+            }
+        });
+    }
+
+    @Test
+    public void testDoNotRecoverViaPositionOnSwapAdapterWithRecycle() throws Throwable {
+        testDoNotRecoverViaPositionOnNewDataSet(new RecyclerViewLayoutTest.AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                mRecyclerView.swapAdapter(new FocusTestAdapter(10), true);
+            }
+        });
+    }
+
+    @Test
+    public void testDoNotRecoverViaPositionOnSwapAdapterWithoutRecycle() throws Throwable {
+        testDoNotRecoverViaPositionOnNewDataSet(new RecyclerViewLayoutTest.AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                mRecyclerView.swapAdapter(new FocusTestAdapter(10), false);
+            }
+        });
+    }
+
+    public void testDoNotRecoverViaPositionOnNewDataSet(
+            final RecyclerViewLayoutTest.AdapterRunnable runnable) throws Throwable {
+        setupBasic(false);
+        assertThat("test sanity", mAdapter.hasStableIds(), is(false));
+        focusVh(4);
+        mLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    runnable.run(mAdapter);
+                } catch (Throwable throwable) {
+                    postExceptionToInstrumentation(throwable);
+                }
+            }
+        });
+
+        mLayoutManager.waitForLayout(1);
+        RecyclerView.ViewHolder otherVh = mRecyclerView.findViewHolderForAdapterPosition(4);
+        checkForMainThreadException();
+        // even if the VH is re-used, it will be removed-reAdded so focus will go away from it.
+        assertFocus("should not recover focus if data set is badly invalid", otherVh, false);
+
+    }
+
+    @Test
+    public void testDoNotRecoverIfReplacementIsNotFocusable() throws Throwable {
+        final int TYPE_NO_FOCUS = 1001;
+        TestAdapter adapter = new FocusTestAdapter(10) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                if (holder.getItemViewType() == TYPE_NO_FOCUS) {
+                    cast(holder).setFocusable(false);
+                }
+            }
+        };
+        adapter.setHasStableIds(true);
+        setupBasic(adapter);
+        RecyclerView.ViewHolder oldVh = focusVh(3);
+        final long itemId = oldVh.getItemId();
+        mLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mAdapter.mItems.get(3).mType = TYPE_NO_FOCUS;
+                mAdapter.notifyDataSetChanged();
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForItemId(itemId);
+        assertFocus(newVh, false);
+    }
+
+    @NonNull
+    private RecyclerView.ViewHolder focusVh(int pos) throws Throwable {
+        final RecyclerView.ViewHolder oldVh = mRecyclerView.findViewHolderForAdapterPosition(pos);
+        assertThat("test sanity", oldVh, notNullValue());
+        requestFocus(oldVh);
+        assertFocus("test sanity", oldVh, true);
+        getInstrumentation().waitForIdleSync();
+        return oldVh;
+    }
+
+    @Test
+    public void testDoNotOverrideAdapterRequestedFocus() throws Throwable {
+        final AtomicLong toFocusId = new AtomicLong(-1);
+
+        FocusTestAdapter adapter = new FocusTestAdapter(10) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                if (holder.getItemId() == toFocusId.get()) {
+                    try {
+                        requestFocus(holder);
+                    } catch (Throwable throwable) {
+                        postExceptionToInstrumentation(throwable);
+                    }
+                }
+            }
+        };
+        adapter.setHasStableIds(true);
+        toFocusId.set(adapter.mItems.get(3).mId);
+        long firstFocusId = toFocusId.get();
+        setupBasic(adapter);
+        RecyclerView.ViewHolder oldVh = mRecyclerView.findViewHolderForItemId(toFocusId.get());
+        assertFocus(oldVh, true);
+        toFocusId.set(mAdapter.mItems.get(5).mId);
+        mLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mAdapter.mItems.get(3).mType += 2;
+                mAdapter.mItems.get(5).mType += 2;
+                mAdapter.notifyDataSetChanged();
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        RecyclerView.ViewHolder requested = mRecyclerView.findViewHolderForItemId(toFocusId.get());
+        assertFocus(oldVh, false);
+        assertFocus(requested, true);
+        RecyclerView.ViewHolder oldReplacement = mRecyclerView
+                .findViewHolderForItemId(firstFocusId);
+        assertFocus(oldReplacement, false);
+        checkForMainThreadException();
+    }
+
+    @Test
+    public void testDoNotOverrideLayoutManagerRequestedFocus() throws Throwable {
+        final AtomicLong toFocusId = new AtomicLong(-1);
+        FocusTestAdapter adapter = new FocusTestAdapter(10);
+        adapter.setHasStableIds(true);
+
+        FocusLayoutManager lm = new FocusLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, 0, state.getItemCount());
+                RecyclerView.ViewHolder toFocus = mRecyclerView
+                        .findViewHolderForItemId(toFocusId.get());
+                if (toFocus != null) {
+                    try {
+                        requestFocus(toFocus);
+                    } catch (Throwable throwable) {
+                        postExceptionToInstrumentation(throwable);
+                    }
+                }
+                layoutLatch.countDown();
+            }
+        };
+
+        toFocusId.set(adapter.mItems.get(3).mId);
+        long firstFocusId = toFocusId.get();
+        setupBasic(adapter, lm);
+
+        RecyclerView.ViewHolder oldVh = mRecyclerView.findViewHolderForItemId(toFocusId.get());
+        assertFocus(oldVh, true);
+        toFocusId.set(mAdapter.mItems.get(5).mId);
+        mLayoutManager.expectLayouts(1);
+        requestLayoutOnUIThread(mRecyclerView);
+        mLayoutManager.waitForLayout(2);
+        RecyclerView.ViewHolder requested = mRecyclerView.findViewHolderForItemId(toFocusId.get());
+        assertFocus(oldVh, false);
+        assertFocus(requested, true);
+        RecyclerView.ViewHolder oldReplacement = mRecyclerView
+                .findViewHolderForItemId(firstFocusId);
+        assertFocus(oldReplacement, false);
+        checkForMainThreadException();
+    }
+
+    private void requestFocus(RecyclerView.ViewHolder viewHolder) throws Throwable {
+        FocusViewHolder fvh = cast(viewHolder);
+        requestFocus(fvh.getViewToFocus(), false);
+    }
+
+    private void assertFocus(RecyclerView.ViewHolder viewHolder, boolean hasFocus) {
+        assertFocus("", viewHolder, hasFocus);
+    }
+
+    private void assertFocus(String msg, RecyclerView.ViewHolder vh, boolean hasFocus) {
+        FocusViewHolder fvh = cast(vh);
+        assertThat(msg, fvh.getViewToFocus().hasFocus(), is(hasFocus));
+    }
+
+    private <T extends FocusViewHolder> T cast(RecyclerView.ViewHolder vh) {
+        assertThat(vh, instanceOf(FocusViewHolder.class));
+        //noinspection unchecked
+        return (T) vh;
+    }
+
+    private class FocusTestAdapter extends TestAdapter {
+
+        public FocusTestAdapter(int count) {
+            super(count);
+        }
+
+        @Override
+        public FocusViewHolder onCreateViewHolder(ViewGroup parent,
+                int viewType) {
+            final FocusViewHolder fvh;
+            if (mFocusOnChild) {
+                fvh = new FocusViewHolderWithChildren(
+                        LayoutInflater.from(parent.getContext())
+                                .inflate(R.layout.focus_test_item_view, parent, false));
+            } else {
+                fvh = new SimpleFocusViewHolder(new TextView(parent.getContext()));
+            }
+            fvh.setFocusable(true);
+            return fvh;
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder holder, int position) {
+            cast(holder).bindTo(mItems.get(position));
+        }
+    }
+
+    private class FocusLayoutManager extends TestLayoutManager {
+        @Override
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            detachAndScrapAttachedViews(recycler);
+            layoutRange(recycler, 0, state.getItemCount());
+            layoutLatch.countDown();
+        }
+    }
+
+    private class FocusViewHolderWithChildren extends FocusViewHolder {
+        public final ViewGroup root;
+        public final ViewGroup parent1;
+        public final ViewGroup parent2;
+        public final TextView textView;
+
+        public FocusViewHolderWithChildren(View view) {
+            super(view);
+            root = (ViewGroup) view;
+            parent1 = (ViewGroup) root.findViewById(R.id.parent1);
+            parent2 = (ViewGroup) root.findViewById(R.id.parent2);
+            textView = (TextView) root.findViewById(R.id.text_view);
+
+        }
+
+        @Override
+        void setFocusable(boolean focusable) {
+            parent1.setFocusableInTouchMode(focusable);
+            parent2.setFocusableInTouchMode(focusable);
+            textView.setFocusableInTouchMode(focusable);
+            root.setFocusableInTouchMode(focusable);
+
+            parent1.setFocusable(focusable);
+            parent2.setFocusable(focusable);
+            textView.setFocusable(focusable);
+            root.setFocusable(focusable);
+        }
+
+        @Override
+        void onBind(Item item) {
+            textView.setText(getText(item));
+        }
+
+        @Override
+        View getViewToFocus() {
+            return textView;
+        }
+    }
+
+    private class SimpleFocusViewHolder extends FocusViewHolder {
+
+        public SimpleFocusViewHolder(View itemView) {
+            super(itemView);
+        }
+
+        @Override
+        void setFocusable(boolean focusable) {
+            itemView.setFocusableInTouchMode(focusable);
+            itemView.setFocusable(focusable);
+        }
+
+        @Override
+        View getViewToFocus() {
+            return itemView;
+        }
+
+        @Override
+        void onBind(Item item) {
+            ((TextView) (itemView)).setText(getText(item));
+        }
+    }
+
+    private abstract class FocusViewHolder extends TestViewHolder {
+
+        public FocusViewHolder(View itemView) {
+            super(itemView);
+        }
+
+        protected String getText(Item item) {
+            return item.mText + "(" + item.mId + ")";
+        }
+
+        abstract void setFocusable(boolean focusable);
+
+        abstract View getViewToFocus();
+
+        abstract void onBind(Item item);
+
+        final void bindTo(Item item) {
+            mBoundItem = item;
+            onBind(item);
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index be43820..3402a3f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -25,6 +25,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import android.content.Context;
+import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.graphics.Color;
 import android.graphics.PointF;
@@ -35,6 +37,7 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v7.util.TouchUtils;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -60,6 +63,7 @@
 import static android.support.v7.widget.RecyclerView.getChildViewHolderInt;
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
+import static org.hamcrest.CoreMatchers.is;
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
@@ -77,6 +81,147 @@
     }
 
     @Test
+    public void boundingBoxNoTranslation() throws Throwable {
+        transformedBoundingBoxTest(new ViewRunnable() {
+            @Override
+            public void run(View view) throws RuntimeException {
+                view.layout(10, 10, 30, 50);
+                assertThat(getTransformedBoundingBox(view), is(new Rect(10, 10, 30, 50)));
+            }
+        });
+    }
+
+    @Test
+    public void boundingBoxTranslateX() throws Throwable {
+        transformedBoundingBoxTest(new ViewRunnable() {
+            @Override
+            public void run(View view) throws RuntimeException {
+                view.layout(10, 10, 30, 50);
+                ViewCompat.setTranslationX(view, 10);
+                assertThat(getTransformedBoundingBox(view), is(new Rect(20, 10, 40, 50)));
+            }
+        });
+    }
+
+    @Test
+    public void boundingBoxTranslateY() throws Throwable {
+        transformedBoundingBoxTest(new ViewRunnable() {
+            @Override
+            public void run(View view) throws RuntimeException {
+                view.layout(10, 10, 30, 50);
+                ViewCompat.setTranslationY(view, 10);
+                assertThat(getTransformedBoundingBox(view), is(new Rect(10, 20, 30, 60)));
+            }
+        });
+    }
+
+    @Test
+    public void boundingBoxScaleX() throws Throwable {
+        transformedBoundingBoxTest(new ViewRunnable() {
+            @Override
+            public void run(View view) throws RuntimeException {
+                view.layout(10, 10, 30, 50);
+                ViewCompat.setScaleX(view, 2);
+                assertThat(getTransformedBoundingBox(view), is(new Rect(0, 10, 40, 50)));
+            }
+        });
+    }
+
+    @Test
+    public void boundingBoxScaleY() throws Throwable {
+        transformedBoundingBoxTest(new ViewRunnable() {
+            @Override
+            public void run(View view) throws RuntimeException {
+                view.layout(10, 10, 30, 50);
+                ViewCompat.setScaleY(view, 2);
+                assertThat(getTransformedBoundingBox(view), is(new Rect(10, -10, 30, 70)));
+            }
+        });
+    }
+
+    @Test
+    public void boundingBoxRotated() throws Throwable {
+        transformedBoundingBoxTest(new ViewRunnable() {
+            @Override
+            public void run(View view) throws RuntimeException {
+                view.layout(10, 10, 30, 50);
+                ViewCompat.setRotation(view, 90);
+                assertThat(getTransformedBoundingBox(view), is(new Rect(0, 20, 40, 40)));
+            }
+        });
+    }
+
+    @Test
+    public void boundingBoxRotatedWithDecorOffsets() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final TestAdapter adapter = new TestAdapter(1);
+        recyclerView.setAdapter(adapter);
+        recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+            @Override
+            public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                    RecyclerView.State state) {
+                outRect.set(1, 2, 3, 4);
+            }
+        });
+        TestLayoutManager layoutManager = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                View view = recycler.getViewForPosition(0);
+                addView(view);
+                view.measure(
+                        View.MeasureSpec.makeMeasureSpec(20, View.MeasureSpec.EXACTLY),
+                        View.MeasureSpec.makeMeasureSpec(40, View.MeasureSpec.EXACTLY)
+                );
+                // trigger decor offsets calculation
+                calculateItemDecorationsForChild(view, new Rect());
+                view.layout(10, 10, 30, 50);
+                ViewCompat.setRotation(view, 90);
+                assertThat(RecyclerViewLayoutTest.this.getTransformedBoundingBox(view),
+                        is(new Rect(-4, 19, 42, 43)));
+
+                layoutLatch.countDown();
+            }
+        };
+        recyclerView.setLayoutManager(layoutManager);
+        layoutManager.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        layoutManager.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    private Rect getTransformedBoundingBox(View child) {
+        Rect rect = new Rect();
+        mRecyclerView.getLayoutManager().getTransformedBoundingBox(child, true, rect);
+        return rect;
+    }
+
+    public void transformedBoundingBoxTest(final ViewRunnable layout) throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final TestAdapter adapter = new TestAdapter(1);
+        recyclerView.setAdapter(adapter);
+        TestLayoutManager layoutManager = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                View view = recycler.getViewForPosition(0);
+                addView(view);
+                view.measure(
+                        View.MeasureSpec.makeMeasureSpec(20, View.MeasureSpec.EXACTLY),
+                        View.MeasureSpec.makeMeasureSpec(40, View.MeasureSpec.EXACTLY)
+                );
+                layout.run(view);
+                layoutLatch.countDown();
+            }
+        };
+        recyclerView.setLayoutManager(layoutManager);
+        layoutManager.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        layoutManager.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    @Test
     public void flingFrozen() throws Throwable {
         testScrollFrozen(true);
     }
@@ -123,6 +268,54 @@
         });
     }
 
+    @Test
+    public void reattachAndScrollCrash() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final TestLayoutManager tlm = new TestLayoutManager() {
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                layoutRange(recycler, 0, Math.min(state.getItemCount(), 10));
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                                          RecyclerView.State state) {
+                // Access views in the state (that might have been deleted).
+                for (int  i = 10; i < state.getItemCount(); i++) {
+                    recycler.getViewForPosition(i);
+                }
+                return dy;
+            }
+        };
+
+        final TestAdapter adapter = new TestAdapter(12);
+
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(tlm);
+
+        setRecyclerView(recyclerView);
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().mContainer.removeView(recyclerView);
+                getActivity().mContainer.addView(recyclerView);
+                try {
+                    adapter.deleteAndNotify(1, adapter.getItemCount() - 1);
+                } catch (Throwable throwable) {
+                    postExceptionToInstrumentation(throwable);
+                }
+                recyclerView.scrollBy(0, 10);
+            }
+        });
+    }
+
     private void testScrollFrozen(boolean fling) throws Throwable {
         RecyclerView recyclerView = new RecyclerView(getActivity());
 
@@ -312,6 +505,87 @@
     }
 
     @Test
+    public void noLayoutIf0ItemsAreChanged() throws Throwable {
+        unnecessaryNotifyEvents(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.notifyItemRangeChanged(3, 0);
+            }
+        });
+    }
+
+    @Test
+    public void noLayoutIf0ItemsAreChangedWithPayload() throws Throwable {
+        unnecessaryNotifyEvents(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.notifyItemRangeChanged(0, 0, new Object());
+            }
+        });
+    }
+
+    @Test
+    public void noLayoutIf0ItemsAreAdded() throws Throwable {
+        unnecessaryNotifyEvents(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.notifyItemRangeInserted(3, 0);
+            }
+        });
+    }
+
+    @Test
+    public void noLayoutIf0ItemsAreRemoved() throws Throwable {
+        unnecessaryNotifyEvents(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.notifyItemRangeRemoved(3, 0);
+            }
+        });
+    }
+
+    @Test
+    public void noLayoutIfItemMovedIntoItsOwnPlace() throws Throwable {
+        unnecessaryNotifyEvents(new AdapterRunnable() {
+            @Override
+            public void run(TestAdapter adapter) throws Throwable {
+                adapter.notifyItemMoved(3, 3);
+            }
+        });
+    }
+
+    public void unnecessaryNotifyEvents(final AdapterRunnable action) throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final TestAdapter adapter = new TestAdapter(5);
+        TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                layoutLatch.countDown();
+            }
+        };
+        recyclerView.setLayoutManager(tlm);
+        recyclerView.setAdapter(adapter);
+        tlm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        tlm.waitForLayout(1);
+        // ready
+        tlm.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    action.run(adapter);
+                } catch (Throwable throwable) {
+                    postExceptionToInstrumentation(throwable);
+                }
+            }
+        });
+        tlm.assertNoLayout("dummy event should not trigger a layout", 1);
+        checkForMainThreadException();
+    }
+
+    @Test
     public void scrollToPositionCallback() throws Throwable {
         RecyclerView recyclerView = new RecyclerView(getActivity());
         TestLayoutManager tlm = new TestLayoutManager() {
@@ -1064,6 +1338,147 @@
         checkForMainThreadException();
     }
 
+    @Test
+    public void duplicateAdapterPositionTest() throws Throwable {
+        final TestAdapter testAdapter = new TestAdapter(10);
+        final TestLayoutManager tlm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, 0, state.getItemCount());
+                if (!state.isPreLayout()) {
+                    while (!recycler.getScrapList().isEmpty()) {
+                        RecyclerView.ViewHolder viewHolder = recycler.getScrapList().get(0);
+                        addDisappearingView(viewHolder.itemView, 0);
+                    }
+                }
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public boolean supportsPredictiveItemAnimations() {
+                return true;
+            }
+        };
+        final DefaultItemAnimator animator = new DefaultItemAnimator();
+        animator.setSupportsChangeAnimations(true);
+        animator.setChangeDuration(10000);
+        testAdapter.setHasStableIds(true);
+        final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
+        recyclerView.setLayoutManager(tlm);
+        recyclerView.setAdapter(testAdapter);
+        recyclerView.setItemAnimator(animator);
+
+        tlm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        tlm.waitForLayout(2);
+
+        tlm.expectLayouts(2);
+        testAdapter.mItems.get(2).mType += 2;
+        final int itemId = testAdapter.mItems.get(2).mId;
+        testAdapter.changeAndNotify(2, 1);
+        tlm.waitForLayout(2);
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertThat("test sanity", recyclerView.getChildCount(), CoreMatchers.is(11));
+                // now mangle the order and run the test
+                RecyclerView.ViewHolder hidden = null;
+                RecyclerView.ViewHolder updated = null;
+                for (int i = 0; i < recyclerView.getChildCount(); i ++) {
+                    View view = recyclerView.getChildAt(i);
+                    RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
+                    if (vh.getAdapterPosition() == 2) {
+                        if (mRecyclerView.mChildHelper.isHidden(view)) {
+                            assertThat(hidden, CoreMatchers.nullValue());
+                            hidden = vh;
+                        } else {
+                            assertThat(updated, CoreMatchers.nullValue());
+                            updated = vh;
+                        }
+                    }
+                }
+                assertThat(hidden, CoreMatchers.notNullValue());
+                assertThat(updated, CoreMatchers.notNullValue());
+
+                mRecyclerView.eatRequestLayout();
+
+                // first put the hidden child back
+                int index1 = mRecyclerView.indexOfChild(hidden.itemView);
+                int index2 = mRecyclerView.indexOfChild(updated.itemView);
+                if (index1 < index2) {
+                    // swap views
+                    swapViewsAtIndices(recyclerView, index1, index2);
+                }
+                assertThat(tlm.findViewByPosition(2), CoreMatchers.sameInstance(updated.itemView));
+
+                assertThat(recyclerView.findViewHolderForAdapterPosition(2),
+                        CoreMatchers.sameInstance(updated));
+                assertThat(recyclerView.findViewHolderForLayoutPosition(2),
+                        CoreMatchers.sameInstance(updated));
+                assertThat(recyclerView.findViewHolderForItemId(itemId),
+                        CoreMatchers.sameInstance(updated));
+
+                // now swap back
+                swapViewsAtIndices(recyclerView, index1, index2);
+
+                assertThat(tlm.findViewByPosition(2), CoreMatchers.sameInstance(updated.itemView));
+                assertThat(recyclerView.findViewHolderForAdapterPosition(2),
+                        CoreMatchers.sameInstance(updated));
+                assertThat(recyclerView.findViewHolderForLayoutPosition(2),
+                        CoreMatchers.sameInstance(updated));
+                assertThat(recyclerView.findViewHolderForItemId(itemId),
+                        CoreMatchers.sameInstance(updated));
+
+                // now remove updated. re-assert fallback to the hidden one
+                tlm.removeView(updated.itemView);
+
+                assertThat(tlm.findViewByPosition(2), CoreMatchers.nullValue());
+                assertThat(recyclerView.findViewHolderForAdapterPosition(2),
+                        CoreMatchers.sameInstance(hidden));
+                assertThat(recyclerView.findViewHolderForLayoutPosition(2),
+                        CoreMatchers.sameInstance(hidden));
+                assertThat(recyclerView.findViewHolderForItemId(itemId),
+                        CoreMatchers.sameInstance(hidden));
+            }
+        });
+
+    }
+
+    private void swapViewsAtIndices(TestRecyclerView recyclerView, int index1, int index2) {
+        if (index1 == index2) {
+            return;
+        }
+        if (index2 < index1) {
+            int tmp = index1;
+            index1 = index2;
+            index2 = tmp;
+        }
+        final View v1 = recyclerView.getChildAt(index1);
+        final View v2 = recyclerView.getChildAt(index2);
+        boolean v1Hidden = recyclerView.mChildHelper.isHidden(v1);
+        boolean v2Hidden = recyclerView.mChildHelper.isHidden(v2);
+        // must unhide before swap otherwise bucket indices will become invalid.
+        if (v1Hidden) {
+            mRecyclerView.mChildHelper.unhide(v1);
+        }
+        if (v2Hidden) {
+            mRecyclerView.mChildHelper.unhide(v2);
+        }
+        recyclerView.detachViewFromParent(index2);
+        recyclerView.attachViewToParent(v2, index1, v2.getLayoutParams());
+        recyclerView.detachViewFromParent(index1 + 1);
+        recyclerView.attachViewToParent(v1, index2, v1.getLayoutParams());
+
+        if (v1Hidden) {
+            mRecyclerView.mChildHelper.hide(v1);
+        }
+        if (v2Hidden) {
+            mRecyclerView.mChildHelper.hide(v2);
+        }
+    }
+
     public void adapterPositionsTest(final AdapterRunnable adapterChanges) throws Throwable {
         final TestAdapter testAdapter = new TestAdapter(10);
         TestLayoutManager tlm = new TestLayoutManager() {
@@ -2167,6 +2582,7 @@
 
     public void adapterChangeInMainThreadTest(String msg,
             final Runnable onLayoutRunnable) throws Throwable {
+        setIgnoreMainThreadException(true);
         final AtomicBoolean doneFirstLayout = new AtomicBoolean(false);
         TestAdapter testAdapter = new TestAdapter(10);
         TestLayoutManager lm = new TestLayoutManager() {
@@ -2198,8 +2614,7 @@
         lm.waitForLayout(2);
         removeRecyclerView();
         assertTrue("Invalid data updates should be caught:" + msg,
-                mainThreadException instanceof IllegalStateException);
-        mainThreadException = null;
+                getMainThreadException() instanceof IllegalStateException);
     }
 
     @Test
@@ -2237,6 +2652,7 @@
 
     public void adapterChangeDuringScrollTest(String msg, final int orientation,
             final Runnable onScrollRunnable) throws Throwable {
+        setIgnoreMainThreadException(true);
         TestAdapter testAdapter = new TestAdapter(100);
         TestLayoutManager lm = new TestLayoutManager() {
             @Override
@@ -2295,8 +2711,7 @@
         lm.waitForLayout(2);
         removeRecyclerView();
         assertTrue("Invalid data updates should be caught:" + msg,
-                mainThreadException instanceof IllegalStateException);
-        mainThreadException = null;
+                getMainThreadException() instanceof IllegalStateException);
     }
 
     @Test
@@ -2816,7 +3231,7 @@
                 getActivity().mContainer.addView(recyclerView);
             }
         });
-        testLayoutManager.waitForLayout(2, TimeUnit.SECONDS);
+        testLayoutManager.waitForLayout(2);
 
         assertEquals("item count in state should be correct", adapter.getItemCount()
                 , itemCount.get());
@@ -3289,6 +3704,48 @@
         jumpingJackSmoothScrollerTest(false);
     }
 
+    @Test
+    public void testScrollByBeforeFirstLayout() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        TestAdapter adapter = new TestAdapter(10);
+        recyclerView.setLayoutManager(new TestLayoutManager() {
+            AtomicBoolean didLayout = new AtomicBoolean(false);
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                didLayout.set(true);
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                assertThat("should run layout before scroll",
+                        didLayout.get(), CoreMatchers.is(true));
+                return super.scrollVerticallyBy(dy, recycler, state);
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+        });
+        recyclerView.setAdapter(adapter);
+
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    setRecyclerView(recyclerView);
+                    recyclerView.scrollBy(10, 19);
+                } catch (Throwable throwable) {
+                    postExceptionToInstrumentation(throwable);
+                }
+            }
+        });
+
+        checkForMainThreadException();
+    }
+
     private void jumpingJackSmoothScrollerTest(final boolean succeed) throws Throwable {
         final List<Integer> receivedScrollToPositions = new ArrayList<>();
         final TestAdapter testAdapter = new TestAdapter(200);
@@ -3413,7 +3870,7 @@
         }
     }
 
-    private static interface AdapterRunnable {
+    public interface AdapterRunnable {
 
         void run(TestAdapter adapter) throws Throwable;
     }
@@ -3425,4 +3882,36 @@
             layoutLatch.countDown();
         }
     }
+
+    /**
+     * Proxy class to make protected methods public
+     */
+    public static class TestRecyclerView extends RecyclerView {
+
+        public TestRecyclerView(Context context) {
+            super(context);
+        }
+
+        public TestRecyclerView(Context context, @Nullable AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public TestRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+
+        @Override
+        public void detachViewFromParent(int index) {
+            super.detachViewFromParent(index);
+        }
+
+        @Override
+        public void attachViewToParent(View child, int index, ViewGroup.LayoutParams params) {
+            super.attachViewToParent(child, index, params);
+        }
+    }
+
+    private static interface ViewRunnable {
+        void run(View view) throws RuntimeException;
+    }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ScrollToPositionWithAutoMeasure.java b/v7/recyclerview/tests/src/android/support/v7/widget/ScrollToPositionWithAutoMeasure.java
new file mode 100644
index 0000000..81fa0a7
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/ScrollToPositionWithAutoMeasure.java
@@ -0,0 +1,103 @@
+/*
+ * 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.widget;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.graphics.Rect;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests scroll to position with wrap content to make sure that LayoutManagers can keep track of
+ * the position if layout is called multiple times.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ScrollToPositionWithAutoMeasure extends BaseRecyclerViewInstrumentationTest {
+    @Test
+    public void testLinearLayoutManager() throws Throwable {
+        LinearLayoutManager llm = new LinearLayoutManager(getActivity());
+        llm.ensureLayoutState();
+        test(llm, llm.mOrientationHelper);
+    }
+
+    @Test
+    public void testGridLayoutManager() throws Throwable {
+        GridLayoutManager glm = new GridLayoutManager(getActivity(), 3);
+        glm.ensureLayoutState();
+        test(glm, glm.mOrientationHelper);
+    }
+
+    @Test
+    public void testStaggeredGridLayoutManager() throws Throwable {
+        StaggeredGridLayoutManager sglm = new StaggeredGridLayoutManager(3,
+                StaggeredGridLayoutManager.VERTICAL);
+        test(sglm, sglm.mPrimaryOrientation);
+    }
+
+    public void test(final RecyclerView.LayoutManager llm,
+            final OrientationHelper orientationHelper) throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setLayoutManager(llm);
+        recyclerView.setAdapter(new TestAdapter(1000));
+        setRecyclerView(recyclerView);
+        getInstrumentation().waitForIdleSync();
+        assertThat("Test sanity", recyclerView.getChildCount() > 0, is(true));
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                View lastChild = llm.getChildAt(llm.getChildCount() - 1);
+                int lastChildPos = recyclerView.getChildAdapterPosition(lastChild);
+                int targetPos = lastChildPos * 2;
+                llm.scrollToPosition(targetPos);
+                recyclerView.measure(
+                        makeMeasureSpec(recyclerView.getWidth(), EXACTLY),
+                        makeMeasureSpec(recyclerView.getHeight(), AT_MOST));
+                assertThat(recyclerView.findViewHolderForAdapterPosition(targetPos),
+                        notNullValue());
+                // make sure it is still visible from top at least
+                int size = orientationHelper.getDecoratedMeasurement(
+                        recyclerView.findViewHolderForAdapterPosition(targetPos).itemView);
+                recyclerView.measure(
+                        makeMeasureSpec(recyclerView.getWidth(), EXACTLY),
+                        makeMeasureSpec(size + 1, EXACTLY));
+                recyclerView.layout(0, 0, recyclerView.getMeasuredWidth(),
+                        recyclerView.getMeasuredHeight());
+                assertThat(recyclerView.findViewHolderForAdapterPosition(targetPos),
+                        notNullValue());
+                RecyclerView.ViewHolder viewHolder =
+                        recyclerView.findViewHolderForAdapterPosition(targetPos);
+                assertThat(viewHolder, notNullValue());
+                Rect viewBounds = new Rect();
+                llm.getDecoratedBoundsWithMargins(viewHolder.itemView, viewBounds);
+                Rect rvBounds = new Rect(0, 0, llm.getWidth(), llm.getHeight());
+                assertThat(rvBounds.contains(viewBounds), is(true));
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
index 15331b5..f8d11d8 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
@@ -25,9 +25,11 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewParent;
 
 import java.util.Arrays;
 import java.util.BitSet;
@@ -38,8 +40,14 @@
 import static android.support.v7.widget.LayoutState.LAYOUT_START;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
 import static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(Parameterized.class)
@@ -731,6 +739,78 @@
         consistentRelayoutTest(mConfig, true);
     }
 
+    @Test
+    public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable {
+        final Config config = ((Config) mConfig.clone()).itemCount(1000);
+        setupByConfig(config);
+        waitFirstLayout();
+        // pick position from child count so that it is not too far away
+        int pos = mRecyclerView.getChildCount() * 2;
+        smoothScrollToPosition(pos, true);
+        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(pos);
+        OrientationHelper helper = mLayoutManager.mPrimaryOrientation;
+        int gap = helper.getDecoratedStart(vh.itemView);
+        scrollBy(gap);
+        gap = helper.getDecoratedStart(vh.itemView);
+        assertThat("test sanity", gap, is(0));
+
+        final int size = helper.getDecoratedMeasurement(vh.itemView);
+        AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (mConfig.mOrientation == HORIZONTAL) {
+                    ViewCompat.setTranslationX(vh.itemView, size * 2);
+                } else {
+                    ViewCompat.setTranslationY(vh.itemView, size * 2);
+                }
+            }
+        });
+        scrollBy(size * 2);
+        assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
+        assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
+        assertThat(vh.getAdapterPosition(), is(pos));
+        scrollBy(size * 2);
+        assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
+    }
+
+    @Test
+    public void dontRecycleViewsTranslatedOutOfBoundsFromEnd() throws Throwable {
+        final Config config = ((Config) mConfig.clone()).itemCount(1000);
+        setupByConfig(config);
+        waitFirstLayout();
+        // pick position from child count so that it is not too far away
+        int pos = mRecyclerView.getChildCount() * 2;
+        mLayoutManager.expectLayouts(1);
+        scrollToPosition(pos);
+        mLayoutManager.waitForLayout(2);
+        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(pos);
+        OrientationHelper helper = mLayoutManager.mPrimaryOrientation;
+        int gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
+        scrollBy(-gap);
+        gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
+        assertThat("test sanity", gap, is(0));
+
+        final int size = helper.getDecoratedMeasurement(vh.itemView);
+        AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (mConfig.mOrientation == HORIZONTAL) {
+                    ViewCompat.setTranslationX(vh.itemView, -size * 2);
+                } else {
+                    ViewCompat.setTranslationY(vh.itemView, -size * 2);
+                }
+            }
+        });
+        scrollBy(-size * 2);
+        assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
+        assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
+        assertThat(vh.getAdapterPosition(), is(pos));
+        scrollBy(-size * 2);
+        assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
+    }
+
     public void consistentRelayoutTest(Config config, boolean firstChildMultiSpan)
             throws Throwable {
         setupByConfig(config);
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
index 6fc9d5d..85b6763 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
@@ -798,12 +798,10 @@
                 }
                 mIncLoaded = true;
             }
-            if (mIncDev == 0) {
-                mIncDev = nIncDeviceCreate();
-            }
             if (mIncCon == 0) {
                 //Create a dummy compat context (synchronous).
-                mIncCon = nIncContextCreate(mIncDev, 0, 0, 0);
+                long device = nIncDeviceCreate();
+                mIncCon = nIncContextCreate(device, 0, 0, 0);
             }
             return rsnScriptIntrinsicCreate(mIncCon, id, eid, mUseInc);
         } else {
@@ -1046,10 +1044,8 @@
         return rsnIncAllocationCreateTyped(mContext, mIncCon, alloc, type, xBytesSize);
     }
 
-    long     mDev;
     long     mContext;
     //Dummy device & context for Inc Support Lib
-    long     mIncDev;
     long     mIncCon;
     //indicator of whether inc support lib has been loaded or not.
     boolean  mIncLoaded;
@@ -1350,7 +1346,6 @@
                 mNativeLibDir = mApplicationContext.getApplicationInfo().nativeLibraryDir;
             }
         }
-        mIncDev = 0;
         mIncCon = 0;
         mIncLoaded = false;
         mRWLock = new ReentrantReadWriteLock();
@@ -1481,8 +1476,8 @@
             }
         }
 
-        rs.mDev = rs.nDeviceCreate();
-        rs.mContext = rs.nContextCreate(rs.mDev, 0, sdkVersion, ct.mID, rs.mNativeLibDir);
+        long device = rs.nDeviceCreate();
+        rs.mContext = rs.nContextCreate(device, 0, sdkVersion, ct.mID, rs.mNativeLibDir);
         rs.mContextType = ct;
         rs.mContextFlags = flags;
         rs.mContextSdkVersion = sdkVersion;
@@ -1703,12 +1698,6 @@
         }
 
         nContextDestroy();
-        nDeviceDestroy(mDev);
-        if (mIncDev != 0) {
-            nIncDeviceDestroy(mIncDev);
-            mIncDev = 0;
-        }
-        mDev = 0;
     }
 
     boolean isAlive() {