Merge "Further clarify docs for AmbientMode." into oc-mr1-jetpack-dev
diff --git a/annotations/build.gradle b/annotations/build.gradle
index af4174d..dd4a5bc 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -27,12 +27,6 @@
     }
 }
 
-// custom tasks for creating source/javadoc jars
-task sourcesJar(type: Jar, dependsOn:classes) {
-    classifier = 'sources'
-    from sourceSets.main.allSource
-}
-
 task javadocJar(type: Jar, dependsOn:javadoc) {
     classifier         'javadoc'
     from               javadoc.destinationDir
@@ -46,7 +40,6 @@
 // add javadoc/source/annotations jar tasks as artifacts
 artifacts {
     archives jar
-    archives sourcesJar
     archives javadocJar
     archives annotationsZip
 }
diff --git a/app-toolkit/core-testing/build.gradle b/app-toolkit/core-testing/build.gradle
index 44d5ff0..127078d 100644
--- a/app-toolkit/core-testing/build.gradle
+++ b/app-toolkit/core-testing/build.gradle
@@ -39,9 +39,7 @@
 dependencies {
     compile project(":arch:runtime")
     compile libs.support.annotations
-    compile(libs.junit) {
-        exclude module: 'hamcrest-core'
-    }
+    compile(libs.junit)
     compile libs.mockito_core, { exclude group: 'net.bytebuddy' }
 
     testCompile libs.junit
diff --git a/app-toolkit/gradle/wrapper/gradle-wrapper.properties b/app-toolkit/gradle/wrapper/gradle-wrapper.properties
index d90766a..9d09896 100644
--- a/app-toolkit/gradle/wrapper/gradle-wrapper.properties
+++ b/app-toolkit/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=../../../../../tools/external/gradle/gradle-4.1-bin.zip
+distributionUrl=../../../../../tools/external/gradle/gradle-4.3-bin.zip
diff --git a/app-toolkit/init.gradle b/app-toolkit/init.gradle
index cd56153..d317d33 100644
--- a/app-toolkit/init.gradle
+++ b/app-toolkit/init.gradle
@@ -147,35 +147,6 @@
         }
     }
 
-    project.plugins.whenPluginAdded { plugin ->
-        def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin"
-                .equals(plugin.class.name)
-        def isJavaLibrary = "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)
-        if (isAndroidLibrary) {
-            // it is an android lib, enable sources.
-            def sourcesTask = project.tasks.create(name: "sourcesJar", type : Jar) {
-                classifier = 'sources'
-                from android.sourceSets.main.getJava().getSrcDirs()
-            }
-            project.artifacts {
-                archives sourcesTask
-            }
-            project.android.defaultConfig {
-                // Update the version meta-data in each Manifest.
-                addManifestPlaceholders(["version" : project.version])
-            }
-        } else if(isJavaLibrary && project.name == "common") {
-            // it is a shared lib, enable sources.
-            def sourcesTask = project.tasks.create(name: "sourcesJar", type : Jar) {
-                classifier = 'sources'
-                from sourceSets.main.allSource
-            }
-            project.artifacts {
-                archives sourcesTask
-            }
-        }
-    }
-
     project.tasks.whenTaskAdded { task ->
         if (task.name.startsWith("assembleAndroidTest")) {
             buildServerAnchorTask.dependsOn task
diff --git a/buildSrc/src/main/groovy/android/support/FlatfootAndroidLibraryPlugin.groovy b/buildSrc/src/main/groovy/android/support/FlatfootAndroidLibraryPlugin.groovy
index c6abac2..47a72d7 100644
--- a/buildSrc/src/main/groovy/android/support/FlatfootAndroidLibraryPlugin.groovy
+++ b/buildSrc/src/main/groovy/android/support/FlatfootAndroidLibraryPlugin.groovy
@@ -16,6 +16,7 @@
 
 package android.support
 
+import com.android.build.gradle.LibraryExtension
 import com.google.common.collect.ImmutableMap
 import org.gradle.api.Plugin
 import org.gradle.api.Project
@@ -33,5 +34,8 @@
         VersionFileWriterTask.setUpAndroidLibrary(project);
 
         project.apply(ImmutableMap.of("plugin", "com.android.library"));
+
+        LibraryExtension library = project.extensions.findByType(LibraryExtension.class);
+        SourceJarTaskHelper.setUpAndroidProject(project, library);
     }
 }
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/android/support/SupportAndroidLibraryPlugin.groovy b/buildSrc/src/main/groovy/android/support/SupportAndroidLibraryPlugin.groovy
index 2170a50..86469ee 100644
--- a/buildSrc/src/main/groovy/android/support/SupportAndroidLibraryPlugin.groovy
+++ b/buildSrc/src/main/groovy/android/support/SupportAndroidLibraryPlugin.groovy
@@ -135,21 +135,7 @@
             project.uploadArchives.dependsOn "lintRelease"
         }
 
-        // Create sources jar for release builds
-        library.getLibraryVariants().all(new Action<LibraryVariant>() {
-            @Override
-            public void execute(LibraryVariant libraryVariant) {
-                if (!libraryVariant.getBuildType().getName().equals(BuilderConstants.RELEASE)) {
-                    return; // Skip non-release builds.
-                }
-
-                Jar sourceJar = project.getTasks().create("sourceJarRelease", Jar.class);
-                sourceJar.preserveFileTimestamps = false;
-                sourceJar.setClassifier("sources");
-                sourceJar.from(library.getSourceSets().findByName("main").getJava().getSrcDirs());
-                project.getArtifacts().add("archives", sourceJar);
-            }
-        });
+        SourceJarTaskHelper.setUpAndroidProject(project, library);
 
         final ErrorProneToolChain toolChain = ErrorProneToolChain.create(project);
         library.getBuildTypes().create("errorProne")
diff --git a/buildSrc/src/main/groovy/android/support/SupportJavaLibraryPlugin.groovy b/buildSrc/src/main/groovy/android/support/SupportJavaLibraryPlugin.groovy
index ae0d55c..3113e6c 100644
--- a/buildSrc/src/main/groovy/android/support/SupportJavaLibraryPlugin.groovy
+++ b/buildSrc/src/main/groovy/android/support/SupportJavaLibraryPlugin.groovy
@@ -20,6 +20,7 @@
 import org.gradle.api.JavaVersion
 import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPluginConvention
 
 /**
  * Support java library specific plugin that sets common configurations needed for
@@ -41,5 +42,7 @@
                 targetCompatibility = version
             }
         }
+
+        SourceJarTaskHelper.setUpJavaProject(project);
     }
 }
\ No newline at end of file
diff --git a/buildSrc/src/main/java/android/support/SourceJarTaskHelper.java b/buildSrc/src/main/java/android/support/SourceJarTaskHelper.java
new file mode 100644
index 0000000..9fbd1db
--- /dev/null
+++ b/buildSrc/src/main/java/android/support/SourceJarTaskHelper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support;
+
+import com.android.build.gradle.LibraryExtension;
+import com.android.builder.core.BuilderConstants;
+
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.bundling.Jar;
+
+/**
+ * Helper class to handle creation of source jars.
+ */
+public class SourceJarTaskHelper {
+    /**
+     * Sets up a source jar task for an Android library project.
+     */
+    public static void setUpAndroidProject(Project project, LibraryExtension extension) {
+        // Create sources jar for release builds
+        extension.getLibraryVariants().all(libraryVariant -> {
+            if (!libraryVariant.getBuildType().getName().equals(BuilderConstants.RELEASE)) {
+                return; // Skip non-release builds.
+            }
+
+            Jar sourceJar = project.getTasks().create("sourceJarRelease", Jar.class);
+            sourceJar.setPreserveFileTimestamps(false);
+            sourceJar.setClassifier("sources");
+            sourceJar.from(extension.getSourceSets().findByName("main").getJava().getSrcDirs());
+            project.getArtifacts().add("archives", sourceJar);
+        });
+    }
+
+    /**
+     * Sets up a source jar task for a Java library project.
+     */
+    public static void setUpJavaProject(Project project) {
+        Jar sourceJar = project.getTasks().create("sourceJar", Jar.class);
+        sourceJar.setPreserveFileTimestamps(false);
+        sourceJar.setClassifier("sources");
+        JavaPluginConvention convention =
+                project.getConvention().getPlugin(JavaPluginConvention.class);
+        sourceJar.from(convention.getSourceSets().findByName("main").getAllSource().getSrcDirs());
+        project.getArtifacts().add("archives", sourceJar);
+    }
+}
diff --git a/car/res/anim/fade_in_trans_left.xml b/car/res/anim/fade_in_trans_left.xml
new file mode 100644
index 0000000..2d6bab5
--- /dev/null
+++ b/car/res/anim/fade_in_trans_left.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:duration="@android:integer/config_shortAnimTime">
+    <translate
+        android:interpolator="@android:interpolator/decelerate_quint"
+        android:fromXDelta="-10%p"
+        android:toXDelta="0" />
+
+    <alpha
+        android:fromAlpha="0.2"
+        android:toAlpha="1"
+        android:interpolator="@android:interpolator/decelerate_quint" />
+</set>
diff --git a/car/res/anim/fade_in_trans_left_layout_anim.xml b/car/res/anim/fade_in_trans_left_layout_anim.xml
new file mode 100644
index 0000000..e7660db
--- /dev/null
+++ b/car/res/anim/fade_in_trans_left_layout_anim.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<layoutAnimation
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:animation="@anim/fade_in_trans_left"
+    android:delay="0%"
+    android:animationOrder="normal" />
diff --git a/car/res/anim/fade_in_trans_right.xml b/car/res/anim/fade_in_trans_right.xml
new file mode 100644
index 0000000..5cbeb59
--- /dev/null
+++ b/car/res/anim/fade_in_trans_right.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:duration="@android:integer/config_shortAnimTime">
+    <translate
+        android:interpolator="@android:interpolator/decelerate_quint"
+        android:fromXDelta="10%p"
+        android:toXDelta="0" />
+
+    <alpha
+        android:fromAlpha="0.2"
+        android:toAlpha="1"
+        android:interpolator="@android:interpolator/decelerate_quint" />
+</set>
diff --git a/car/res/anim/fade_in_trans_right_layout_anim.xml b/car/res/anim/fade_in_trans_right_layout_anim.xml
new file mode 100644
index 0000000..b76de23
--- /dev/null
+++ b/car/res/anim/fade_in_trans_right_layout_anim.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<layoutAnimation
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:animation="@anim/fade_in_trans_right"
+    android:delay="0%"
+    android:animationOrder="normal" />
diff --git a/car/res/values/attrs.xml b/car/res/values/attrs.xml
index 17a1960..0ba8f55 100644
--- a/car/res/values/attrs.xml
+++ b/car/res/values/attrs.xml
@@ -45,6 +45,12 @@
         <attr name="dividerStartMargin" format="dimension" />
         <!-- The width of the margin on the right side of the list -->
         <attr name="listEndMargin" format="dimension" />
+        <!-- An optional spacing between items in the list -->
+        <attr name="itemSpacing" format="dimension" />
+        <!-- The icon to be used for the up button of the scroll bar. -->
+        <attr name="upButtonIcon" format="reference" />
+        <!-- The icon to be used for the down button of the scroll bar.  -->
+        <attr name="downButtonIcon" format="reference" />
     </declare-styleable>
 
     <!-- The attributes for customizing the appearance of the hamburger and back arrow in the
diff --git a/car/res/values/colors.xml b/car/res/values/colors.xml
index 2099ae3..11880c3 100644
--- a/car/res/values/colors.xml
+++ b/car/res/values/colors.xml
@@ -19,14 +19,43 @@
          http://www.google.com/design/spec/style/color.html#color-ui-color-palette -->
     <color name="car_grey_50">#fffafafa</color>
     <color name="car_grey_100">#fff5f5f5</color>
+    <color name="car_grey_200">#ffeeeeee</color>
     <color name="car_grey_300">#ffe0e0e0</color>
+    <color name="car_grey_400">#ffbdbdbd</color>
     <color name="car_grey_500">#ff9e9e9e</color>
+    <color name="car_grey_600">#ff757575</color>
+    <color name="car_grey_650">#ff6B6B6B</color>
+    <color name="car_grey_700">#ff616161</color>
     <color name="car_grey_800">#ff424242</color>
     <color name="car_grey_900">#ff212121</color>
-
-    <!-- Car specific colors that are not from the color palette. -->
-    <color name="car_grey_650">#ff6B6B6B</color>
-    <color name="car_darkbluegrey_700">#ff172026</color>
+    <color name="car_grey_1000">#cc000000</color>
+    <color name="car_white_1000">#1effffff</color>
+    <color name="car_blue_grey_800">#ff37474F</color>
+    <color name="car_blue_grey_900">#ff263238</color>
+    <color name="car_dark_blue_grey_600">#ff1d272d</color>
+    <color name="car_dark_blue_grey_700">#ff172026</color>
+    <color name="car_dark_blue_grey_800">#ff11181d</color>
+    <color name="car_dark_blue_grey_900">#ff0c1013</color>
+    <color name="car_dark_blue_grey_1000">#ff090c0f</color>
+    <color name="car_light_blue_300">#ff4fc3f7</color>
+    <color name="car_light_blue_500">#ff03A9F4</color>
+    <color name="car_light_blue_600">#ff039be5</color>
+    <color name="car_light_blue_700">#ff0288d1</color>
+    <color name="car_light_blue_800">#ff0277bd</color>
+    <color name="car_light_blue_900">#ff01579b</color>
+    <color name="car_blue_300">#ff91a7ff</color>
+    <color name="car_blue_500">#ff5677fc</color>
+    <color name="car_green_500">#ff0f9d58</color>
+    <color name="car_green_700">#ff0b8043</color>
+    <color name="car_yellow_500">#fff4b400</color>
+    <color name="car_yellow_800">#ffee8100</color>
+    <color name="car_red_400">#ffe06055</color>
+    <color name="car_red_500">#ffdb4437</color>
+    <color name="car_red_500a">#ffd50000</color>
+    <color name="car_red_700">#ffc53929</color>
+    <color name="car_teal_200">#ff80cbc4</color>
+    <color name="car_teal_700">#ff00796b</color>
+    <color name="car_indigo_800">#ff283593</color>
 
     <!--  Various colors for text sizes. "Light" and "dark" here refer to the lighter or darker
           shades. -->
@@ -88,7 +117,7 @@
 
     <!-- A light and dark colored card. -->
     <color name="car_card_light">@color/car_grey_50</color>
-    <color name="car_card_dark">@color/car_darkbluegrey_700</color>
+    <color name="car_card_dark">@color/car_dark_blue_grey_700</color>
 
     <!-- The default color of a card in car UI. -->
     <color name="car_card">@color/car_card_light</color>
diff --git a/car/src/main/java/android/support/car/drawer/CarDrawerController.java b/car/src/main/java/android/support/car/drawer/CarDrawerController.java
index 4d9f4e9..823abd8 100644
--- a/car/src/main/java/android/support/car/drawer/CarDrawerController.java
+++ b/car/src/main/java/android/support/car/drawer/CarDrawerController.java
@@ -19,16 +19,19 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.support.annotation.AnimRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.car.R;
 import android.support.car.widget.PagedListView;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.Toolbar;
 import android.view.Gravity;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.animation.AnimationUtils;
 import android.widget.ProgressBar;
 
 import java.util.Stack;
@@ -39,13 +42,21 @@
  * navigation.
  */
 public class CarDrawerController {
+    /** An animation for when a user navigates into a submenu. */
+    @AnimRes
+    private static final int DRILL_DOWN_ANIM = R.anim.fade_in_trans_right_layout_anim;
+
+    /** An animation for when a user navigates up (when the back button is pressed). */
+    @AnimRes
+    private static final int NAVIGATE_UP_ANIM = R.anim.fade_in_trans_left_layout_anim;
+
     /** The amount that the drawer has been opened before its color should be switched. */
     private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
 
     /**
      * A representation of the hierarchy of navigation being displayed in the list. The ordering of
      * this stack is the order that the user has visited each level. When the user navigates up,
-     * the adapters are poopped from this list.
+     * the adapters are popped from this list.
      */
     private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
 
@@ -78,16 +89,14 @@
             ActionBarDrawerToggle drawerToggle) {
         mToolbar = toolbar;
         mContext = drawerLayout.getContext();
-
+        mDrawerToggle = drawerToggle;
         mDrawerLayout = drawerLayout;
 
         mDrawerContent = drawerLayout.findViewById(R.id.drawer_content);
         mDrawerList = drawerLayout.findViewById(R.id.drawer_list);
         mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
-
         mProgressBar = drawerLayout.findViewById(R.id.drawer_progress);
 
-        mDrawerToggle = drawerToggle;
         setupDrawerToggling();
     }
 
@@ -124,6 +133,7 @@
         mAdapterStack.peek().setTitleChangeListener(null);
         mAdapterStack.push(adapter);
         switchToAdapterInternal(adapter);
+        runLayoutAnimation(DRILL_DOWN_ANIM);
     }
 
     /** Close the drawer. */
@@ -272,7 +282,6 @@
         // NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
         // car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts.
         mDrawerList.getRecyclerView().setAdapter(adapter);
-        scrollToPosition(0);
     }
 
     /**
@@ -291,6 +300,7 @@
         adapter.setTitleChangeListener(null);
         adapter.cleanup();
         switchToAdapterInternal(mAdapterStack.peek());
+        runLayoutAnimation(NAVIGATE_UP_ANIM);
         return true;
     }
 
@@ -302,5 +312,17 @@
             adapter.cleanup();
         }
         switchToAdapterInternal(mAdapterStack.peek());
+        runLayoutAnimation(NAVIGATE_UP_ANIM);
+    }
+
+    /**
+     * Runs the given layout animation on the PagedListView. Running this animation will also
+     * refresh the contents of the list.
+     */
+    private void runLayoutAnimation(@AnimRes int animation) {
+        RecyclerView recyclerView = mDrawerList.getRecyclerView();
+        recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, animation));
+        recyclerView.getAdapter().notifyDataSetChanged();
+        recyclerView.scheduleLayoutAnimation();
     }
 }
diff --git a/car/src/main/java/android/support/car/widget/PagedListView.java b/car/src/main/java/android/support/car/widget/PagedListView.java
index 4652700..97a7041 100644
--- a/car/src/main/java/android/support/car/widget/PagedListView.java
+++ b/car/src/main/java/android/support/car/widget/PagedListView.java
@@ -23,6 +23,8 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.support.annotation.IdRes;
 import android.support.annotation.NonNull;
@@ -180,6 +182,11 @@
                     dividerStartId, dividerEndId));
         }
 
+        int itemSpacing = a.getDimensionPixelSize(R.styleable.PagedListView_itemSpacing, 0);
+        if (itemSpacing > 0) {
+            mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
+        }
+
         // Set this to true so that this view consumes clicks events and views underneath
         // don't receive this click event. Without this it's possible to click places in the
         // view that don't capture the event, and as a result, elements visually hidden consume
@@ -212,6 +219,16 @@
                     }
                 });
 
+        Drawable upButtonIcon = a.getDrawable(R.styleable.PagedListView_upButtonIcon);
+        if (upButtonIcon != null) {
+            setUpButtonIcon(upButtonIcon);
+        }
+
+        Drawable downButtonIcon = a.getDrawable(R.styleable.PagedListView_downButtonIcon);
+        if (downButtonIcon != null) {
+            setDownButtonIcon(downButtonIcon);
+        }
+
         mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE);
 
         // Modify the layout the Scroll Bar is not visible.
@@ -312,6 +329,16 @@
         mHandler.post(mUpdatePaginationRunnable);
     }
 
+    /** Sets the icon to be used for the up button. */
+    public void setUpButtonIcon(Drawable icon) {
+        mScrollBarView.setUpButtonIcon(icon);
+    }
+
+    /** Sets the icon to be used for the down button. */
+    public void setDownButtonIcon(Drawable icon) {
+        mScrollBarView.setDownButtonIcon(icon);
+    }
+
     /**
      * Sets the adapter for the list.
      *
@@ -422,6 +449,32 @@
     }
 
     /**
+     * Sets spacing between each item in the list. The spacing will not be added before the first
+     * item and after the last.
+     *
+     * @param itemSpacing the spacing between each item.
+     */
+    public void setItemSpacing(int itemSpacing) {
+        ItemSpacingDecoration existing = null;
+        for (int i = 0, count = mRecyclerView.getItemDecorationCount(); i < count; i++) {
+            RecyclerView.ItemDecoration itemDecoration = mRecyclerView.getItemDecorationAt(i);
+            if (itemDecoration instanceof ItemSpacingDecoration) {
+                existing = (ItemSpacingDecoration) itemDecoration;
+                break;
+            }
+        }
+
+        if (itemSpacing == 0 && existing != null) {
+            mRecyclerView.removeItemDecoration(existing);
+        } else if (existing == null) {
+            mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
+        } else {
+            existing.setItemSpacing(itemSpacing);
+        }
+        mRecyclerView.invalidateItemDecorations();
+    }
+
+    /**
      * Adds an {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} to this
      * PagedListView.
      *
@@ -766,16 +819,50 @@
     }
 
     /**
+     * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will add spacing
+     * between each item in the RecyclerView that it is added to.
+     */
+    private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration {
+
+        private int mHalfItemSpacing;
+
+        private ItemSpacingDecoration(int itemSpacing) {
+            mHalfItemSpacing = itemSpacing / 2;
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                RecyclerView.State state) {
+            super.getItemOffsets(outRect, view, parent, state);
+            // Skip top offset for first item and bottom offset for last.
+            int position = parent.getChildAdapterPosition(view);
+            if (position > 0) {
+                outRect.top = mHalfItemSpacing;
+            }
+            if (position < state.getItemCount() - 1) {
+                outRect.bottom = mHalfItemSpacing;
+            }
+        }
+
+        /**
+         * @param itemSpacing sets spacing between each item.
+         */
+        public void setItemSpacing(int itemSpacing) {
+            mHalfItemSpacing = itemSpacing / 2;
+        }
+    }
+
+    /**
      * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will draw a dividing
      * line between each item in the RecyclerView that it is added to.
      */
-    public static class DividerDecoration extends RecyclerView.ItemDecoration {
+    private static class DividerDecoration extends RecyclerView.ItemDecoration {
         private final Context mContext;
         private final Paint mPaint;
         private final int mDividerHeight;
         private final int mDividerStartMargin;
         @IdRes private final int mDividerStartId;
-        @IdRes private final int mDvidierEndId;
+        @IdRes private final int mDividerEndId;
 
         /**
          * @param dividerStartMargin The start offset of the dividing line. This offset will be
@@ -792,7 +879,7 @@
             mContext = context;
             mDividerStartMargin = dividerStartMargin;
             mDividerStartId = dividerStartId;
-            mDvidierEndId = dividerEndId;
+            mDividerEndId = dividerEndId;
 
             Resources res = context.getResources();
             mPaint = new Paint();
@@ -807,16 +894,20 @@
 
         @Override
         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
-            for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++) {
+            // Draw a divider line between each item. No need to draw the line for the last item.
+            for (int i = 0, childCount = parent.getChildCount(); i < childCount - 1; i++) {
                 View container = parent.getChildAt(i);
+                View nextContainer = parent.getChildAt(i + 1);
+                int spacing = nextContainer.getTop() - container.getBottom();
+
                 View startChild =
                         mDividerStartId != INVALID_RESOURCE_ID
                                 ? container.findViewById(mDividerStartId)
                                 : container;
 
                 View endChild =
-                        mDvidierEndId != INVALID_RESOURCE_ID
-                                ? container.findViewById(mDvidierEndId)
+                        mDividerEndId != INVALID_RESOURCE_ID
+                                ? container.findViewById(mDividerEndId)
                                 : container;
 
                 if (startChild == null || endChild == null) {
@@ -825,14 +916,24 @@
 
                 int left = mDividerStartMargin + startChild.getLeft();
                 int right = endChild.getRight();
-                int bottom = container.getBottom();
+                int bottom = container.getBottom() + spacing / 2 + mDividerHeight / 2;
                 int top = bottom - mDividerHeight;
 
-                // Draw a divider line between each item. No need to draw the line for the last
-                // item.
-                if (i != childCount - 1) {
-                    c.drawRect(left, top, right, bottom, mPaint);
-                }
+                c.drawRect(left, top, right, bottom, mPaint);
+            }
+        }
+
+        @Override
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+                RecyclerView.State state) {
+            super.getItemOffsets(outRect, view, parent, state);
+            // Skip top offset for first item and bottom offset for last.
+            int position = parent.getChildAdapterPosition(view);
+            if (position > 0) {
+                outRect.top = mDividerHeight / 2;
+            }
+            if (position < state.getItemCount() - 1) {
+                outRect.bottom = mDividerHeight / 2;
             }
         }
     }
diff --git a/car/src/main/java/android/support/car/widget/PagedScrollBarView.java b/car/src/main/java/android/support/car/widget/PagedScrollBarView.java
index 125b354..be1d18b 100644
--- a/car/src/main/java/android/support/car/widget/PagedScrollBarView.java
+++ b/car/src/main/java/android/support/car/widget/PagedScrollBarView.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
 import android.support.car.R;
 import android.support.v4.content.ContextCompat;
 import android.util.AttributeSet;
@@ -98,6 +99,16 @@
         return true;
     }
 
+    /** Sets the icon to be used for the up button. */
+    public void setUpButtonIcon(Drawable icon) {
+        mUpButton.setImageDrawable(icon);
+    }
+
+    /** Sets the icon to be used for the down button. */
+    public void setDownButtonIcon(Drawable icon) {
+        mDownButton.setImageDrawable(icon);
+    }
+
     /**
      * Sets the listener that will be notified when the up and down buttons have been pressed.
      *
diff --git a/car/tests/AndroidManifest.xml b/car/tests/AndroidManifest.xml
index abd5264..f58390f 100644
--- a/car/tests/AndroidManifest.xml
+++ b/car/tests/AndroidManifest.xml
@@ -22,4 +22,4 @@
         <activity android:name="android.support.car.widget.ColumnCardViewTestActivity"/>
         <activity android:name="android.support.car.widget.PagedListViewTestActivity"/>
     </application>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/car/tests/res/drawable/ic_thumb_down.xml b/car/tests/res/drawable/ic_thumb_down.xml
new file mode 100644
index 0000000..25fccdb
--- /dev/null
+++ b/car/tests/res/drawable/ic_thumb_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M30,6L12,6c-1.66,0 -3.08,1.01 -3.68,2.44l-6.03,14.1C2.11,23 2,23.49 2,24v3.83l0.02,0.02L2,28c0,2.21 1.79,4 4,4h12.63l-1.91,9.14c-0.04,0.2 -0.07,0.41 -0.07,0.63 0,0.83 0.34,1.58 0.88,2.12L19.66,46l13.17,-13.17C33.55,32.1 34,31.1 34,30L34,10c0,-2.21 -1.79,-4 -4,-4zM38,6v24h8L46,6h-8z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/car/tests/res/drawable/ic_thumb_up.xml b/car/tests/res/drawable/ic_thumb_up.xml
new file mode 100644
index 0000000..9f02cf3
--- /dev/null
+++ b/car/tests/res/drawable/ic_thumb_up.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:pathData="M2,42h8L10,18L2,18v24zM46,20c0,-2.21 -1.79,-4 -4,-4L29.37,16l1.91,-9.14c0.04,-0.2 0.07,-0.41 0.07,-0.63 0,-0.83 -0.34,-1.58 -0.88,-2.12L28.34,2 15.17,15.17C14.45,15.9 14,16.9 14,18v20c0,2.21 1.79,4 4,4h18c1.66,0 3.08,-1.01 3.68,-2.44l6.03,-14.1c0.18,-0.46 0.29,-0.95 0.29,-1.46v-3.83l-0.02,-0.02L46,20z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/car/tests/res/layout/activity_paged_list_view.xml b/car/tests/res/layout/activity_paged_list_view.xml
index ca8ba38..d14eb96 100644
--- a/car/tests/res/layout/activity_paged_list_view.xml
+++ b/car/tests/res/layout/activity_paged_list_view.xml
@@ -26,7 +26,5 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:showPagedListViewDivider="false"
-        app:offsetScrollBar="true"
-    />
-
-</FrameLayout>
\ No newline at end of file
+        app:offsetScrollBar="true"/>
+</FrameLayout>
diff --git a/car/tests/src/android/support/car/widget/PagedListViewTest.java b/car/tests/src/android/support/car/widget/PagedListViewTest.java
index 4048434..bd673ad 100644
--- a/car/tests/src/android/support/car/widget/PagedListViewTest.java
+++ b/car/tests/src/android/support/car/widget/PagedListViewTest.java
@@ -35,15 +35,20 @@
 import static org.junit.Assert.assertThat;
 
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
 import android.support.car.test.R;
+import android.support.test.annotation.UiThreadTest;
 import android.support.test.espresso.IdlingRegistry;
 import android.support.test.espresso.IdlingResource;
+import android.support.test.espresso.matcher.ViewMatchers;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.junit.After;
@@ -72,11 +77,13 @@
     public ActivityTestRule<PagedListViewTestActivity> mActivityRule =
             new ActivityTestRule<>(PagedListViewTestActivity.class);
 
+    private PagedListViewTestActivity mActivity;
     private PagedListView mPagedListView;
 
     @Before
     public void setUp() {
-        mPagedListView = mActivityRule.getActivity().findViewById(R.id.paged_list_view);
+        mActivity = mActivityRule.getActivity();
+        mPagedListView = mActivity.findViewById(R.id.paged_list_view);
     }
 
     @After
@@ -253,6 +260,73 @@
         assertThat(mPagedListView.getFirstFullyVisibleChildPosition(), is(equalTo(topPosition)));
     }
 
+    @Test
+    public void setItemSpacing() throws Throwable {
+        final int itemCount = 3;
+        setUpPagedListView(itemCount /* itemCount */);
+
+        // Initial spacing is 0.
+        final View[] views = new View[itemCount];
+        mActivityRule.runOnUiThread(() -> {
+            for (int i = 0; i < itemCount; i++) {
+                views[i] = mPagedListView.findViewByPosition(i);
+            }
+        });
+        for (int i = 0; i < itemCount - 1; i++) {
+            assertThat(views[i + 1].getTop() - views[i].getBottom(), is(equalTo(0)));
+        }
+
+        // Setting item spacing causes layout change.
+        // Implicitly wait for layout by making two calls in UI thread.
+        final int itemSpacing = 10;
+        mActivityRule.runOnUiThread(() -> {
+            mPagedListView.setItemSpacing(itemSpacing);
+        });
+        mActivityRule.runOnUiThread(() -> {
+            for (int i = 0; i < itemCount; i++) {
+                views[i] = mPagedListView.findViewByPosition(i);
+            }
+        });
+        for (int i = 0; i < itemCount - 1; i++) {
+            assertThat(views[i + 1].getTop() - views[i].getBottom(), is(equalTo(itemSpacing)));
+        }
+
+        // Re-setting spacing back to 0 also works.
+        mActivityRule.runOnUiThread(() -> {
+            mPagedListView.setItemSpacing(0);
+        });
+        mActivityRule.runOnUiThread(() -> {
+            for (int i = 0; i < itemCount; i++) {
+                views[i] = mPagedListView.findViewByPosition(i);
+            }
+        });
+        for (int i = 0; i < itemCount - 1; i++) {
+            assertThat(views[i + 1].getTop() - views[i].getBottom(), is(equalTo(0)));
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    public void testSetScrollBarButtonIcons() throws Throwable {
+        // Set up a pagedListView with a large item count to ensure the scroll bar buttons are
+        // always showing.
+        setUpPagedListView(100 /* itemCount */);
+
+        Drawable upDrawable = mActivity.getDrawable(R.drawable.ic_thumb_up);
+        mPagedListView.setUpButtonIcon(upDrawable);
+
+        ImageView upButton = mPagedListView.findViewById(R.id.page_up);
+        ViewMatchers.assertThat(upButton.getDrawable().getConstantState(),
+                is(equalTo(upDrawable.getConstantState())));
+
+        Drawable downDrawable = mActivity.getDrawable(R.drawable.ic_thumb_down);
+        mPagedListView.setDownButtonIcon(downDrawable);
+
+        ImageView downButton = mPagedListView.findViewById(R.id.page_down);
+        ViewMatchers.assertThat(downButton.getDrawable().getConstantState(),
+                is(equalTo(downDrawable.getConstantState())));
+    }
+
     private static String itemText(int index) {
         return "Data " + index;
     }
diff --git a/compat/src/main/java/android/support/v4/view/ViewCompat.java b/compat/src/main/java/android/support/v4/view/ViewCompat.java
index 34a198a..204a121 100644
--- a/compat/src/main/java/android/support/v4/view/ViewCompat.java
+++ b/compat/src/main/java/android/support/v4/view/ViewCompat.java
@@ -1356,7 +1356,7 @@
                 // after applying the tint
                 Drawable background = view.getBackground();
                 boolean hasTint = (view.getBackgroundTintList() != null)
-                        && (view.getBackgroundTintMode() != null);
+                        || (view.getBackgroundTintMode() != null);
                 if ((background != null) && hasTint) {
                     if (background.isStateful()) {
                         background.setState(view.getDrawableState());
@@ -1375,7 +1375,7 @@
                 // after applying the tint
                 Drawable background = view.getBackground();
                 boolean hasTint = (view.getBackgroundTintList() != null)
-                        && (view.getBackgroundTintMode() != null);
+                        || (view.getBackgroundTintMode() != null);
                 if ((background != null) && hasTint) {
                     if (background.isStateful()) {
                         background.setState(view.getDrawableState());
diff --git a/emoji/appcompat/src/main/java/android/support/text/emoji/widget/EmojiAppCompatEditText.java b/emoji/appcompat/src/main/java/android/support/text/emoji/widget/EmojiAppCompatEditText.java
index 87c17c2..0ae4ea0 100644
--- a/emoji/appcompat/src/main/java/android/support/text/emoji/widget/EmojiAppCompatEditText.java
+++ b/emoji/appcompat/src/main/java/android/support/text/emoji/widget/EmojiAppCompatEditText.java
@@ -21,6 +21,7 @@
 import android.support.annotation.Nullable;
 import android.support.text.emoji.EmojiCompat;
 import android.support.v7.widget.AppCompatEditText;
+import android.text.method.KeyListener;
 import android.util.AttributeSet;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -67,8 +68,11 @@
     }
 
     @Override
-    public void setKeyListener(android.text.method.KeyListener input) {
-        super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input));
+    public void setKeyListener(@Nullable KeyListener keyListener) {
+        if (keyListener != null) {
+            keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+        }
+        super.setKeyListener(keyListener);
     }
 
     @Override
diff --git a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
index a0e8a69..70ca7a6 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiEditText.java
@@ -21,6 +21,7 @@
 import android.support.annotation.IntRange;
 import android.support.annotation.Nullable;
 import android.support.text.emoji.EmojiCompat;
+import android.text.method.KeyListener;
 import android.util.AttributeSet;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -73,8 +74,11 @@
     }
 
     @Override
-    public void setKeyListener(android.text.method.KeyListener keyListener) {
-        super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
+    public void setKeyListener(@Nullable KeyListener keyListener) {
+        if (keyListener != null) {
+            keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+        }
+        super.setKeyListener(keyListener);
     }
 
     @Override
diff --git a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiExtractEditText.java b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiExtractEditText.java
index ca1868e..2e4d3ca 100644
--- a/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiExtractEditText.java
+++ b/emoji/core/src/main/java/android/support/text/emoji/widget/EmojiExtractEditText.java
@@ -27,6 +27,7 @@
 import android.support.annotation.RestrictTo;
 import android.support.text.emoji.EmojiCompat;
 import android.support.text.emoji.EmojiSpan;
+import android.text.method.KeyListener;
 import android.util.AttributeSet;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -81,8 +82,11 @@
     }
 
     @Override
-    public void setKeyListener(android.text.method.KeyListener keyListener) {
-        super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
+    public void setKeyListener(@Nullable KeyListener keyListener) {
+        if (keyListener != null) {
+            keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+        }
+        super.setKeyListener(keyListener);
     }
 
     @Override
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextTest.java
index e4b452c..3528095 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiEditTextTest.java
@@ -21,11 +21,13 @@
 
 import static junit.framework.Assert.assertEquals;
 
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 
 import android.annotation.TargetApi;
 import android.app.Instrumentation;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.rule.ActivityTestRule;
@@ -82,6 +84,15 @@
     }
 
     @Test
+    @UiThreadTest
+    public void testSetKeyListener_withNull() {
+        final TestActivity activity = mActivityRule.getActivity();
+        final EmojiEditText editText = activity.findViewById(R.id.editTextWithMaxCount);
+        editText.setKeyListener(null);
+        assertNull(editText.getKeyListener());
+    }
+
+    @Test
     @SdkSuppress(minSdkVersion = 19)
     @TargetApi(19)
     public void testSetMaxCount() {
diff --git a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiExtractTextLayoutTest.java b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiExtractTextLayoutTest.java
index 8e1c6cf..6ed9b8e 100644
--- a/emoji/core/tests/java/android/support/text/emoji/widget/EmojiExtractTextLayoutTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/widget/EmojiExtractTextLayoutTest.java
@@ -18,6 +18,7 @@
 
 import static android.support.text.emoji.util.EmojiMatcher.sameCharSequence;
 
+import static junit.framework.Assert.assertNull;
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
@@ -88,6 +89,21 @@
 
     @Test
     @UiThreadTest
+    public void testSetKeyListener_withNull() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final EmojiExtractTextLayout layout = (EmojiExtractTextLayout) LayoutInflater.from(context)
+                .inflate(android.support.text.emoji.test.R.layout.extract_view, null);
+
+        final EmojiExtractEditText extractEditText = layout.findViewById(
+                android.R.id.inputExtractEditText);
+        assertNotNull(extractEditText);
+
+        extractEditText.setKeyListener(null);
+        assertNull(extractEditText.getKeyListener());
+    }
+
+    @Test
+    @UiThreadTest
     public void testSetEmojiReplaceStrategy() {
         final Context context = InstrumentationRegistry.getTargetContext();
 
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6314766..96e6dab 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-4.1-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-4.3-bin.zip
diff --git a/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java b/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java
index 647d5d7..c1dc54d 100644
--- a/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java
+++ b/lifecycle/extensions/src/test/java/android/arch/lifecycle/LiveDataTest.java
@@ -45,6 +45,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mockito;
 
@@ -52,11 +53,22 @@
 @RunWith(JUnit4.class)
 public class LiveDataTest {
     private PublicLiveData<String> mLiveData;
-    private LifecycleOwner mOwner;
-    private LifecycleOwner mOwner2;
-    private LifecycleRegistry mRegistry;
-    private LifecycleRegistry mRegistry2;
     private MethodExec mActiveObserversChanged;
+
+    private LifecycleOwner mOwner;
+    private LifecycleRegistry mRegistry;
+
+    private LifecycleOwner mOwner2;
+    private LifecycleRegistry mRegistry2;
+
+    private LifecycleOwner mOwner3;
+    private Lifecycle mLifecycle3;
+    private Observer<String> mObserver3;
+
+    private LifecycleOwner mOwner4;
+    private Lifecycle mLifecycle4;
+    private Observer<String> mObserver4;
+
     private boolean mInObserver;
 
     @Before
@@ -67,12 +79,10 @@
         mLiveData.activeObserversChanged = mActiveObserversChanged;
 
         mOwner = mock(LifecycleOwner.class);
-
         mRegistry = new LifecycleRegistry(mOwner);
         when(mOwner.getLifecycle()).thenReturn(mRegistry);
 
         mOwner2 = mock(LifecycleOwner.class);
-
         mRegistry2 = new LifecycleRegistry(mOwner2);
         when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
 
@@ -80,6 +90,19 @@
     }
 
     @Before
+    public void initNonLifecycleRegistry() {
+        mOwner3 = mock(LifecycleOwner.class);
+        mLifecycle3 = mock(Lifecycle.class);
+        mObserver3 = (Observer<String>) mock(Observer.class);
+        when(mOwner3.getLifecycle()).thenReturn(mLifecycle3);
+
+        mOwner4 = mock(LifecycleOwner.class);
+        mLifecycle4 = mock(Lifecycle.class);
+        mObserver4 = (Observer<String>) mock(Observer.class);
+        when(mOwner4.getLifecycle()).thenReturn(mLifecycle4);
+    }
+
+    @Before
     public void swapExecutorDelegate() {
         ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
     }
@@ -572,100 +595,195 @@
         verify(mActiveObserversChanged, never()).onCall(anyBoolean());
     }
 
-    /**
-     * Verifies that if a lifecycle's state changes without an event, and changes to something that
-     * LiveData would become inactive in response to, LiveData will detect the change upon new data
-     * being set and become inactive.  Also verifies that once the lifecycle enters into a state
-     * that LiveData should become active to, that it does indeed become active.
-     */
     @Test
-    public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
-        Observer<String> observer = (Observer<String>) mock(Observer.class);
-        mLiveData.observe(mOwner, observer);
+    public void setValue_lifecycleIsCreatedNoEvent_liveDataBecomesInactiveAndObserverNotCalled() {
 
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        // Arrange.
 
-        // Marking state as CREATED should call onInactive.
+        mLiveData.observe(mOwner3, mObserver3);
+
+        GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
         reset(mActiveObserversChanged);
-        mRegistry.markState(Lifecycle.State.CREATED);
-        verify(mActiveObserversChanged).onCall(false);
-        reset(mActiveObserversChanged);
+        reset(mObserver3);
 
-        // Setting a new value should trigger LiveData to realize the Lifecycle it is observing
-        // is in a state where the LiveData should be inactive, so the LiveData will call onInactive
-        // and the Observer shouldn't be affected.
+        // Act.
+
         mLiveData.setValue("1");
 
-        // state is already CREATED so should not call again
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        verify(observer, never()).onChanged(anyString());
+        // Assert.
 
-        // Sanity check.  Because we've only marked the state as CREATED, sending ON_START
-        // should re-dispatch events.
-        reset(mActiveObserversChanged);
-        reset(observer);
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        verify(mActiveObserversChanged).onCall(true);
-        verify(observer).onChanged("1");
+        verify(mActiveObserversChanged).onCall(false);
+        verify(mObserver3, never()).onChanged(anyString());
     }
 
-    /**
-     *  This test verifies that LiveData will detect changes in LifecycleState that would make it
-     *  inactive upon the setting of new data, but only if all of the Lifecycles it's observing
-     *  are all in those states.  It also makes sure that once it is inactive, that it will become
-     *  active again once one of the lifecycles it's observing moves to an appropriate state.
+    /*
+     * Arrange: LiveData was made inactive via SetValue (because the Lifecycle it was
+     * observing was in the CREATED state and no event was dispatched).
+     * Act: Lifecycle enters Started state and dispatches event.
+     * Assert: LiveData becomes active and dispatches new value to observer.
      */
     @Test
-    public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
-        Observer<String> observer1 = mock(Observer.class);
-        Observer<String> observer2 = mock(Observer.class);
+    public void test_liveDataInactiveViaSetValueThenLifecycleResumes() {
 
-        mLiveData.observe(mOwner, observer1);
-        mLiveData.observe(mOwner2, observer2);
+        // Arrange.
 
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-        mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+        mLiveData.observe(mOwner3, mObserver3);
 
-        // Marking the state to created won't change LiveData to be inactive.
-        reset(mActiveObserversChanged);
-        mRegistry.markState(Lifecycle.State.CREATED);
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
 
-        // After setting a value, the LiveData will stay active because there is still a STARTED
-        // lifecycle being observed.  The one Observer associated with the STARTED lifecycle will
-        // also have been called, but the other Observer will not have been called.
-        reset(observer1);
-        reset(observer2);
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
         mLiveData.setValue("1");
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        verify(observer1, never()).onChanged(anyString());
-        verify(observer2).onChanged("1");
 
-        // Now we set the other Lifecycle to be inactive, live data should become inactive.
-        reset(observer1);
-        reset(observer2);
-        mRegistry2.markState(Lifecycle.State.CREATED);
-        verify(mActiveObserversChanged).onCall(false);
-        verify(observer1, never()).onChanged(anyString());
-        verify(observer2, never()).onChanged(anyString());
-
-        // Now we post another value, because both lifecycles are in the Created state, live data
-        // will not dispatch any values
         reset(mActiveObserversChanged);
-        mLiveData.setValue("2");
-        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-        verify(observer1, never()).onChanged(anyString());
-        verify(observer2, never()).onChanged(anyString());
+        reset(mObserver3);
 
-        // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
-        // be made active and it's associated Observer will be called with the new value, but the
-        // Observer associated with the Lifecycle that is still in the Created state won't be
-        // called.
-        reset(mActiveObserversChanged);
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+        // Act.
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        // Assert.
+
         verify(mActiveObserversChanged).onCall(true);
-        verify(observer1).onChanged("2");
-        verify(observer2, never()).onChanged(anyString());
+        verify(mObserver3).onChanged("1");
+    }
+
+    /*
+     * Arrange: One of two Lifecycles enter the CREATED state without sending an event.
+     * Act: Lifecycle's setValue method is called with new value.
+     * Assert: LiveData stays active and new value is dispatched to Lifecycle that is still at least
+     * STARTED.
+     */
+    @Test
+    public void setValue_oneOfTwoLifecyclesAreCreatedNoEvent() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        mLiveData.setValue("1");
+
+        // Assert.
+
+        verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+        verify(mObserver3, never()).onChanged(anyString());
+        verify(mObserver4).onChanged("1");
+    }
+
+    /*
+     * Arrange: Two observed Lifecycles enter the CREATED state without sending an event.
+     * Act: Lifecycle's setValue method is called with new value.
+     * Assert: LiveData becomes inactive and nothing is dispatched to either observer.
+     */
+    @Test
+    public void setValue_twoLifecyclesAreCreatedNoEvent() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        mLiveData.setValue("1");
+
+        // Assert.
+
+        verify(mActiveObserversChanged).onCall(false);
+        verify(mObserver3, never()).onChanged(anyString());
+        verify(mObserver3, never()).onChanged(anyString());
+    }
+
+    /*
+     * Arrange: LiveData was made inactive via SetValue (because both Lifecycles it was
+     * observing were in the CREATED state and no event was dispatched).
+     * Act: One Lifecycle enters STARTED state and dispatches lifecycle event.
+     * Assert: LiveData becomes active and dispatches new value to observer associated with started
+     * Lifecycle.
+     */
+    @Test
+    public void test_liveDataInactiveViaSetValueThenOneLifecycleResumes() {
+
+        // Arrange.
+
+        mLiveData.observe(mOwner3, mObserver3);
+        mLiveData.observe(mOwner4, mObserver4);
+
+        GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+        GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+        lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+        when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+        mLiveData.setValue("1");
+
+        reset(mActiveObserversChanged);
+        reset(mObserver3);
+        reset(mObserver4);
+
+        // Act.
+
+        when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+        lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+        // Assert.
+
+        verify(mActiveObserversChanged).onCall(true);
+        verify(mObserver3).onChanged("1");
+        verify(mObserver4, never()).onChanged(anyString());
+    }
+
+    private GenericLifecycleObserver getGenericLifecycleObserver(Lifecycle lifecycle) {
+        ArgumentCaptor<GenericLifecycleObserver> captor =
+                ArgumentCaptor.forClass(GenericLifecycleObserver.class);
+        verify(lifecycle).addObserver(captor.capture());
+        return (captor.getValue());
     }
 
     @SuppressWarnings("WeakerAccess")
diff --git a/lifecycle/gradle/wrapper/gradle-wrapper.properties b/lifecycle/gradle/wrapper/gradle-wrapper.properties
index 467c103..6051ae0 100644
--- a/lifecycle/gradle/wrapper/gradle-wrapper.properties
+++ b/lifecycle/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
diff --git a/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java b/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
index 2b25bc9..ba76f8e 100644
--- a/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ b/lifecycle/reactivestreams/src/main/java/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -24,7 +24,7 @@
 import org.reactivestreams.Subscriber;
 import org.reactivestreams.Subscription;
 
-import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Adapts {@link LiveData} input and output to the ReactiveStreams spec.
@@ -53,83 +53,114 @@
     public static <T> Publisher<T> toPublisher(
             final LifecycleOwner lifecycle, final LiveData<T> liveData) {
 
-        return new Publisher<T>() {
+        return new LiveDataPublisher<>(lifecycle, liveData);
+    }
+
+    private static final class LiveDataPublisher<T> implements Publisher<T> {
+        final LifecycleOwner mLifecycle;
+        final LiveData<T> mLiveData;
+
+        LiveDataPublisher(final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+            this.mLifecycle = lifecycle;
+            this.mLiveData = liveData;
+        }
+
+        @Override
+        public void subscribe(Subscriber<? super T> subscriber) {
+            subscriber.onSubscribe(new LiveDataSubscription<T>(subscriber, mLifecycle, mLiveData));
+        }
+
+        static final class LiveDataSubscription<T> implements Subscription, Observer<T> {
+            final Subscriber<? super T> mSubscriber;
+            final LifecycleOwner mLifecycle;
+            final LiveData<T> mLiveData;
+
+            volatile boolean mCanceled;
+            // used on main thread only
             boolean mObserving;
-            boolean mCanceled;
             long mRequested;
+            // used on main thread only
             @Nullable
             T mLatest;
 
+            LiveDataSubscription(final Subscriber<? super T> subscriber,
+                    final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+                this.mSubscriber = subscriber;
+                this.mLifecycle = lifecycle;
+                this.mLiveData = liveData;
+            }
+
             @Override
-            public void subscribe(final Subscriber<? super T> subscriber) {
-                final Observer<T> observer = new Observer<T>() {
+            public void onChanged(T t) {
+                if (mCanceled) {
+                    return;
+                }
+                if (mRequested > 0) {
+                    mLatest = null;
+                    mSubscriber.onNext(t);
+                    if (mRequested != Long.MAX_VALUE) {
+                        mRequested--;
+                    }
+                } else {
+                    mLatest = t;
+                }
+            }
+
+            @Override
+            public void request(final long n) {
+                if (mCanceled) {
+                    return;
+                }
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
                     @Override
-                    public void onChanged(@Nullable T t) {
+                    public void run() {
                         if (mCanceled) {
                             return;
                         }
-                        if (mRequested > 0) {
+                        if (n <= 0L) {
+                            mCanceled = true;
+                            if (mObserving) {
+                                mLiveData.removeObserver(LiveDataSubscription.this);
+                                mObserving = false;
+                            }
                             mLatest = null;
-                            subscriber.onNext(t);
-                            if (mRequested != Long.MAX_VALUE) {
-                                mRequested--;
-                            }
-                        } else {
-                            mLatest = t;
-                        }
-                    }
-                };
-
-                subscriber.onSubscribe(new Subscription() {
-                    @Override
-                    public void request(final long n) {
-                        if (n < 0 || mCanceled) {
+                            mSubscriber.onError(
+                                    new IllegalArgumentException("Non-positive request"));
                             return;
                         }
-                        ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
-                            @Override
-                            public void run() {
-                                if (mCanceled) {
-                                    return;
-                                }
-                                // Prevent overflowage.
-                                mRequested = mRequested + n >= mRequested
-                                        ? mRequested + n : Long.MAX_VALUE;
-                                if (!mObserving) {
-                                    mObserving = true;
-                                    liveData.observe(lifecycle, observer);
-                                } else if (mLatest != null) {
-                                    observer.onChanged(mLatest);
-                                    mLatest = null;
-                                }
-                            }
-                        });
-                    }
 
-                    @Override
-                    public void cancel() {
-                        if (mCanceled) {
-                            return;
+                        // Prevent overflowage.
+                        mRequested = mRequested + n >= mRequested
+                                ? mRequested + n : Long.MAX_VALUE;
+                        if (!mObserving) {
+                            mObserving = true;
+                            mLiveData.observe(mLifecycle, LiveDataSubscription.this);
+                        } else if (mLatest != null) {
+                            onChanged(mLatest);
+                            mLatest = null;
                         }
-                        ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
-                            @Override
-                            public void run() {
-                                if (mCanceled) {
-                                    return;
-                                }
-                                if (mObserving) {
-                                    liveData.removeObserver(observer);
-                                    mObserving = false;
-                                }
-                                mLatest = null;
-                                mCanceled = true;
-                            }
-                        });
                     }
                 });
             }
 
-        };
+            @Override
+            public void cancel() {
+                if (mCanceled) {
+                    return;
+                }
+                mCanceled = true;
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mObserving) {
+                            mLiveData.removeObserver(LiveDataSubscription.this);
+                            mObserving = false;
+                        }
+                        mLatest = null;
+                    }
+                });
+            }
+        }
     }
 
     /**
@@ -145,6 +176,10 @@
      * Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
      * added, it will automatically notify with the last value held in LiveData,
      * which might not be the last value emitted by the Publisher.
+     * <p>
+     * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+     * in the data that's held. In case of an error being emitted by the publisher, an error will
+     * be propagated to the main thread and the app will crash.
      *
      * @param <T> The type of data hold by this instance.
      */
@@ -166,67 +201,80 @@
      * added, it will automatically notify with the last value held in LiveData,
      * which might not be the last value emitted by the Publisher.
      *
+     * <p>
+     * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+     * in the data that's held. In case of an error being emitted by the publisher, an error will
+     * be propagated to the main thread and the app will crash.
+     *
      * @param <T> The type of data hold by this instance.
      */
     private static class PublisherLiveData<T> extends LiveData<T> {
-        private WeakReference<Subscription> mSubscriptionRef;
         private final Publisher mPublisher;
-        private final Object mLock = new Object();
+        final AtomicReference<LiveDataSubscriber> mSubscriber;
 
         PublisherLiveData(@NonNull final Publisher publisher) {
             mPublisher = publisher;
+            mSubscriber = new AtomicReference<>();
         }
 
         @Override
         protected void onActive() {
             super.onActive();
-
-            mPublisher.subscribe(new Subscriber<T>() {
-                @Override
-                public void onSubscribe(Subscription s) {
-                    // Don't worry about backpressure. If the stream is too noisy then
-                    // backpressure can be handled upstream.
-                    synchronized (mLock) {
-                        s.request(Long.MAX_VALUE);
-                        mSubscriptionRef = new WeakReference<>(s);
-                    }
-                }
-
-                @Override
-                public void onNext(final T t) {
-                    postValue(t);
-                }
-
-                @Override
-                public void onError(Throwable t) {
-                    synchronized (mLock) {
-                        mSubscriptionRef = null;
-                    }
-                    // Errors should be handled upstream, so propagate as a crash.
-                    throw new RuntimeException(t);
-                }
-
-                @Override
-                public void onComplete() {
-                    synchronized (mLock) {
-                        mSubscriptionRef = null;
-                    }
-                }
-            });
-
+            LiveDataSubscriber s = new LiveDataSubscriber();
+            mSubscriber.set(s);
+            mPublisher.subscribe(s);
         }
 
         @Override
         protected void onInactive() {
             super.onInactive();
-            synchronized (mLock) {
-                WeakReference<Subscription> subscriptionRef = mSubscriptionRef;
-                if (subscriptionRef != null) {
-                    Subscription subscription = subscriptionRef.get();
-                    if (subscription != null) {
-                        subscription.cancel();
+            LiveDataSubscriber s = mSubscriber.getAndSet(null);
+            if (s != null) {
+                s.cancelSubscription();
+            }
+        }
+
+        final class LiveDataSubscriber extends AtomicReference<Subscription>
+                implements Subscriber<T> {
+
+            @Override
+            public void onSubscribe(Subscription s) {
+                if (compareAndSet(null, s)) {
+                    s.request(Long.MAX_VALUE);
+                } else {
+                    s.cancel();
+                }
+            }
+
+            @Override
+            public void onNext(T item) {
+                postValue(item);
+            }
+
+            @Override
+            public void onError(final Throwable ex) {
+                mSubscriber.compareAndSet(this, null);
+
+                ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Errors should be handled upstream, so propagate as a crash.
+                        throw new RuntimeException("LiveData does not handle errors. Errors from "
+                                + "publishers should be handled upstream and propagated as "
+                                + "state", ex);
                     }
-                    mSubscriptionRef = null;
+                });
+            }
+
+            @Override
+            public void onComplete() {
+                mSubscriber.compareAndSet(this, null);
+            }
+
+            public void cancelSubscription() {
+                Subscription s = get();
+                if (s != null) {
+                    s.cancel();
                 }
             }
         }
diff --git a/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
index 7278847..83e543c 100644
--- a/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
+++ b/lifecycle/reactivestreams/src/test/java/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
@@ -16,6 +16,9 @@
 
 package android.arch.lifecycle;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -34,6 +37,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import io.reactivex.Flowable;
 import io.reactivex.disposables.Disposable;
@@ -115,6 +119,41 @@
     }
 
     @Test
+    public void convertsFromPublisherSubscribeWithDelay() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        processor.delaySubscription(100, TimeUnit.SECONDS, sBackgroundScheduler);
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("foo");
+        liveData.removeObserver(mObserver);
+        sBackgroundScheduler.triggerActions();
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("bar");
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "bar", "baz")));
+    }
+
+    @Test
+    public void convertsFromPublisherThrowsException() {
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        IllegalStateException exception = new IllegalStateException("test exception");
+        try {
+            processor.onError(exception);
+            fail("Runtime Exception expected");
+        } catch (RuntimeException ex) {
+            assertEquals(ex.getCause(), exception);
+        }
+    }
+
+    @Test
     public void convertsFromPublisherWithMultipleObservers() {
         final List<String> output2 = new ArrayList<>();
         PublishProcessor<String> processor = PublishProcessor.create();
@@ -125,7 +164,7 @@
         processor.onNext("foo");
         processor.onNext("bar");
 
-        // The second mObserver should only get the newest value and any later values.
+        // The second observer should only get the newest value and any later values.
         liveData.observe(mLifecycleOwner, new Observer<String>() {
             @Override
             public void onChanged(@Nullable String s) {
@@ -140,6 +179,32 @@
     }
 
     @Test
+    public void convertsFromPublisherWithMultipleObserversAfterInactive() {
+        final List<String> output2 = new ArrayList<>();
+        PublishProcessor<String> processor = PublishProcessor.create();
+        LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+        liveData.observe(mLifecycleOwner, mObserver);
+
+        processor.onNext("foo");
+        processor.onNext("bar");
+
+        // The second observer should only get the newest value and any later values.
+        liveData.observe(mLifecycleOwner, new Observer<String>() {
+            @Override
+            public void onChanged(@Nullable String s) {
+                output2.add(s);
+            }
+        });
+
+        liveData.removeObserver(mObserver);
+        processor.onNext("baz");
+
+        assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar")));
+        assertThat(output2, is(Arrays.asList("bar", "baz")));
+    }
+
+    @Test
     public void convertsFromPublisherAfterInactive() {
         PublishProcessor<String> processor = PublishProcessor.create();
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
@@ -156,7 +221,7 @@
     }
 
     @Test
-    public void convertsFromPublisherManagesSubcriptions() {
+    public void convertsFromPublisherManagesSubscriptions() {
         PublishProcessor<String> processor = PublishProcessor.create();
         LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
 
@@ -198,7 +263,7 @@
 
         assertThat(
                 mOutputProcessor.getValues(new String[]{}),
-                is(new String[] {"foo", "bar", "baz"}));
+                is(new String[]{"foo", "bar", "baz"}));
     }
 
     @Test
@@ -263,10 +328,10 @@
         final Subscription subscription = subscriptionSubject.blockingSingle();
 
         subscription.request(1);
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
 
         liveData.setValue("foo");
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
 
         subscription.request(2);
         liveData.setValue("baz");
@@ -274,7 +339,7 @@
 
         assertThat(
                 mOutputProcessor.getValues(new String[]{}),
-                is(new String[] {"foo", "baz", "fizz"}));
+                is(new String[]{"foo", "baz", "fizz"}));
 
         // 'nyan' will be dropped as there is nothing currently requesting a stream.
         liveData.setValue("nyan");
@@ -282,13 +347,13 @@
 
         assertThat(
                 mOutputProcessor.getValues(new String[]{}),
-                is(new String[] {"foo", "baz", "fizz"}));
+                is(new String[]{"foo", "baz", "fizz"}));
 
         // When a new request comes in, the latest value will be pushed.
         subscription.request(1);
         assertThat(
                 mOutputProcessor.getValues(new String[]{}),
-                is(new String[] {"foo", "baz", "fizz", "cat"}));
+                is(new String[]{"foo", "baz", "fizz", "cat"}));
     }
 
     @Test
@@ -301,17 +366,17 @@
 
         liveData.setValue("foo");
 
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
         sBackgroundScheduler.triggerActions();
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
 
         liveData.setValue("bar");
         liveData.setValue("baz");
 
-        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+        assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
         sBackgroundScheduler.triggerActions();
         assertThat(mOutputProcessor.getValues(
                 new String[]{}),
-                is(new String[] {"foo", "bar", "baz"}));
+                is(new String[]{"foo", "bar", "baz"}));
     }
 }
diff --git a/media-compat/cross-app-tests/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media-compat/cross-app-tests/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
index 23d6d1e..6bfd828 100644
--- a/media-compat/cross-app-tests/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
+++ b/media-compat/cross-app-tests/client/tests/src/android/support/mediacompat/client/MediaBrowserCompatTest.java
@@ -47,6 +47,8 @@
 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_2;
 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_3;
 import static android.support.mediacompat.testlib.MediaBrowserConstants.TEST_VALUE_4;
+import static android.support.mediacompat.testlib.VersionConstants.KEY_SERVICE_VERSION;
+import static android.support.test.InstrumentationRegistry.getArguments;
 import static android.support.test.InstrumentationRegistry.getContext;
 import static android.support.test.InstrumentationRegistry.getInstrumentation;
 
@@ -69,6 +71,7 @@
 import android.support.v4.media.MediaBrowserCompat.MediaItem;
 import android.support.v4.media.MediaBrowserServiceCompat;
 import android.support.v4.media.MediaDescriptionCompat;
+import android.util.Log;
 
 import org.junit.After;
 import org.junit.Before;
@@ -84,6 +87,8 @@
 @RunWith(AndroidJUnit4.class)
 public class MediaBrowserCompatTest {
 
+    private static final String TAG = "MediaBrowserCompatTest";
+
     // The maximum time to wait for an operation.
     private static final long TIME_OUT_MS = 3000L;
     private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 300L;
@@ -107,9 +112,8 @@
                     "android.support.mediacompat.service.test",
                     "android.support.mediacompat.service"
                             + ".StubMediaBrowserServiceCompatWithDelayedMediaSession");
-    private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName(
-            "invalid.package", "invalid.ServiceClassName");
 
+    private String mServiceVersion;
     private MediaBrowserCompat mMediaBrowser;
     private StubConnectionCallback mConnectionCallback;
     private StubSubscriptionCallback mSubscriptionCallback;
@@ -120,6 +124,10 @@
 
     @Before
     public void setUp() {
+        // The version of the service app is provided through the instrumentation arguments.
+        mServiceVersion = getArguments().getString(KEY_SERVICE_VERSION, "");
+        Log.d(TAG, "Service app version: " + mServiceVersion);
+
         mConnectionCallback = new StubConnectionCallback();
         mSubscriptionCallback = new StubSubscriptionCallback();
         mItemCallback = new StubItemCallback();
diff --git a/media-compat/cross-app-tests/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java b/media-compat/cross-app-tests/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
index 3b1e421..6f5e07e 100644
--- a/media-compat/cross-app-tests/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
+++ b/media-compat/cross-app-tests/client/tests/src/android/support/mediacompat/client/MediaControllerCompatCallbackTest.java
@@ -47,6 +47,8 @@
 import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_QUEUE_ID_2;
 import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_SESSION_EVENT;
 import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_VALUE;
+import static android.support.mediacompat.testlib.VersionConstants.KEY_SERVICE_VERSION;
+import static android.support.test.InstrumentationRegistry.getArguments;
 import static android.support.test.InstrumentationRegistry.getContext;
 import static android.support.test.InstrumentationRegistry.getInstrumentation;
 import static android.support.test.InstrumentationRegistry.getTargetContext;
@@ -80,6 +82,7 @@
 import android.support.v4.media.session.MediaSessionCompat.QueueItem;
 import android.support.v4.media.session.ParcelableVolumeInfo;
 import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
 
 import junit.framework.Assert;
 
@@ -96,6 +99,9 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class MediaControllerCompatCallbackTest {
+
+    private static final String TAG = "MediaControllerCompatCallbackTest";
+
     // The maximum time to wait for an operation, that is expected to happen.
     private static final long TIME_OUT_MS = 3000L;
     private static final int MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT = 10;
@@ -107,6 +113,8 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Object mWaitLock = new Object();
 
+    private String mServiceVersion;
+
     // MediaBrowserCompat object to get the session token.
     private MediaBrowserCompat mMediaBrowser;
     private ConnectionCallback mConnectionCallback = new ConnectionCallback();
@@ -116,6 +124,10 @@
 
     @Before
     public void setUp() throws Exception {
+        // The version of the service app is provided through the instrumentation arguments.
+        mServiceVersion = getArguments().getString(KEY_SERVICE_VERSION, "");
+        Log.d(TAG, "Service app version: " + mServiceVersion);
+
         getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
diff --git a/media-compat/cross-app-tests/lib/src/main/java/android/support/mediacompat/testlib/VersionConstants.java b/media-compat/cross-app-tests/lib/src/main/java/android/support/mediacompat/testlib/VersionConstants.java
new file mode 100644
index 0000000..6533ee1
--- /dev/null
+++ b/media-compat/cross-app-tests/lib/src/main/java/android/support/mediacompat/testlib/VersionConstants.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib;
+
+/**
+ * Constants for getting support library version information.
+ */
+public class VersionConstants {
+    public static final String KEY_CLIENT_VERSION = "client_version";
+    public static final String KEY_SERVICE_VERSION = "service_version";
+}
diff --git a/media-compat/cross-app-tests/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java b/media-compat/cross-app-tests/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
index a30f82d..61f98f4 100644
--- a/media-compat/cross-app-tests/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
+++ b/media-compat/cross-app-tests/service/tests/src/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
@@ -50,6 +50,8 @@
 import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_KEY;
 import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_SESSION_TAG;
 import static android.support.mediacompat.testlib.MediaSessionConstants.TEST_VALUE;
+import static android.support.mediacompat.testlib.VersionConstants.KEY_CLIENT_VERSION;
+import static android.support.test.InstrumentationRegistry.getArguments;
 import static android.support.test.InstrumentationRegistry.getContext;
 import static android.support.test.InstrumentationRegistry.getInstrumentation;
 import static android.support.test.InstrumentationRegistry.getTargetContext;
@@ -69,6 +71,7 @@
 import android.support.v4.media.RatingCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
 
 import org.junit.After;
 import org.junit.Before;
@@ -83,6 +86,9 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class MediaSessionCompatCallbackTest {
+
+    private static final String TAG = "MediaSessionCompatCallbackTest";
+
     // The maximum time to wait for an operation.
     private static final long TIME_OUT_MS = 3000L;
     private static final float DELTA = 1e-4f;
@@ -90,11 +96,16 @@
 
     private final Object mWaitLock = new Object();
     private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private String mClientVersion;
     private MediaSessionCompat mSession;
     private MediaSessionCallback mCallback = new MediaSessionCallback();
 
     @Before
     public void setUp() throws Exception {
+        // The version of the client app is provided through the instrumentation arguments.
+        mClientVersion = getArguments().getString(KEY_CLIENT_VERSION, "");
+        Log.d(TAG, "Client app version: " + mClientVersion);
+
         getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
diff --git a/room/gradle/wrapper/gradle-wrapper.properties b/room/gradle/wrapper/gradle-wrapper.properties
index a5292b0..383477d 100644
--- a/room/gradle/wrapper/gradle-wrapper.properties
+++ b/room/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
index 2d79f3e..5423d04 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
@@ -164,8 +164,10 @@
      * Set the presenter selector used to create and bind views.
      */
     public final void setPresenterSelector(PresenterSelector presenterSelector) {
-        mPresenterSelector = presenterSelector;
-        updateAdapter();
+        if (mPresenterSelector != presenterSelector) {
+            mPresenterSelector = presenterSelector;
+            updateAdapter();
+        }
     }
 
     /**
@@ -180,8 +182,10 @@
      * @param rowsAdapter Adapter that represents list of rows.
      */
     public final void setAdapter(ObjectAdapter rowsAdapter) {
-        mAdapter = rowsAdapter;
-        updateAdapter();
+        if (mAdapter != rowsAdapter) {
+            mAdapter = rowsAdapter;
+            updateAdapter();
+        }
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
index dba78da..6a477ab 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -161,8 +161,10 @@
      * Set the presenter selector used to create and bind views.
      */
     public final void setPresenterSelector(PresenterSelector presenterSelector) {
-        mPresenterSelector = presenterSelector;
-        updateAdapter();
+        if (mPresenterSelector != presenterSelector) {
+            mPresenterSelector = presenterSelector;
+            updateAdapter();
+        }
     }
 
     /**
@@ -177,8 +179,10 @@
      * @param rowsAdapter Adapter that represents list of rows.
      */
     public final void setAdapter(ObjectAdapter rowsAdapter) {
-        mAdapter = rowsAdapter;
-        updateAdapter();
+        if (mAdapter != rowsAdapter) {
+            mAdapter = rowsAdapter;
+            updateAdapter();
+        }
     }
 
     /**
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 ae31c4f..b358efc 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -570,14 +570,27 @@
         }
 
         boolean oldIsPageRow = mIsPageRow;
+        Object oldPageRow = mPageRow;
         mIsPageRow = mCanShowHeaders && item instanceof PageRow;
+        mPageRow = mIsPageRow ? item : null;
         boolean swap;
 
         if (mMainFragment == null) {
             swap = true;
         } else {
             if (oldIsPageRow) {
-                swap = true;
+                if (mIsPageRow) {
+                    if (oldPageRow == null) {
+                        // fragment is restored, page row object not yet set, so just set the
+                        // mPageRow object and there is no need to replace the fragment
+                        swap = false;
+                    } else {
+                        // swap if page row object changes
+                        swap = oldPageRow != mPageRow;
+                    }
+                } else {
+                    swap = true;
+                }
             } else {
                 swap = mIsPageRow;
             }
@@ -590,25 +603,29 @@
                         "Fragment must implement MainFragmentAdapterProvider");
             }
 
-            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
-                    .getMainFragmentAdapter();
-            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
-            if (!mIsPageRow) {
-                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
-                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
-                            .getMainFragmentRowsAdapter();
-                } else {
-                    mMainFragmentRowsAdapter = null;
-                }
-                mIsPageRow = mMainFragmentRowsAdapter == null;
-            } else {
-                mMainFragmentRowsAdapter = null;
-            }
+            setMainFragmentAdapter();
         }
 
         return swap;
     }
 
+    void setMainFragmentAdapter() {
+        mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment)
+                .getMainFragmentAdapter();
+        mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+        if (!mIsPageRow) {
+            if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+                setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment)
+                        .getMainFragmentRowsAdapter());
+            } else {
+                setMainFragmentRowsAdapter(null);
+            }
+            mIsPageRow = mMainFragmentRowsAdapter == null;
+        } else {
+            setMainFragmentRowsAdapter(null);
+        }
+    }
+
     /**
      * Factory class responsible for creating fragment given the current item. {@link ListRow}
      * should return {@link RowsFragment} or its subclass whereas {@link PageRow}
@@ -678,7 +695,8 @@
     MainFragmentAdapter mMainFragmentAdapter;
     Fragment mMainFragment;
     HeadersFragment mHeadersFragment;
-    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    ListRowDataAdapter mMainFragmentListRowDataAdapter;
 
     private ObjectAdapter mAdapter;
     private PresenterSelector mAdapterPresenter;
@@ -701,6 +719,7 @@
     private int mSelectedPosition = -1;
     private float mScaleFactor;
     boolean mIsPageRow;
+    Object mPageRow;
 
     private PresenterSelector mHeaderPresenterSelector;
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -820,11 +839,45 @@
             return;
         }
 
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setAdapter(
-                    adapter == null ? null : new ListRowDataAdapter(adapter));
+        updateMainFragmentRowsAdapter();
+        mHeadersFragment.setAdapter(mAdapter);
+    }
+
+    void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+        if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+            return;
         }
-        mHeadersFragment.setAdapter(adapter);
+        // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter
+        if (mMainFragmentRowsAdapter != null) {
+            // RowsFragment cannot change click/select listeners after view created.
+            // The main fragment and adapter should be GCed as long as there is no reference from
+            // BrowseFragment to it.
+            mMainFragmentRowsAdapter.setAdapter(null);
+        }
+        mMainFragmentRowsAdapter = mainFragmentRowsAdapter;
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+        // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter
+        updateMainFragmentRowsAdapter();
+    }
+
+    /**
+     * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter.
+     * It also clears old mMainFragmentListRowDataAdapter.
+     */
+    void updateMainFragmentRowsAdapter() {
+        if (mMainFragmentListRowDataAdapter != null) {
+            mMainFragmentListRowDataAdapter.detach();
+            mMainFragmentListRowDataAdapter = null;
+        }
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentListRowDataAdapter = mAdapter == null
+                    ? null : new ListRowDataAdapter(mAdapter);
+            mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter);
+        }
     }
 
     public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
@@ -1144,7 +1197,8 @@
 
     @Override
     public void onDestroyView() {
-        mMainFragmentRowsAdapter = null;
+        setMainFragmentRowsAdapter(null);
+        mPageRow = null;
         mMainFragmentAdapter = null;
         mMainFragment = null;
         mHeadersFragment = null;
@@ -1198,26 +1252,17 @@
             mHeadersFragment = (HeadersFragment) getChildFragmentManager()
                     .findFragmentById(R.id.browse_headers_dock);
             mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
-            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
-                    .getMainFragmentAdapter();
-            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
 
             mIsPageRow = savedInstanceState != null
                     && savedInstanceState.getBoolean(IS_PAGE_ROW, false);
+            // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is
+            // the case for restoring, later if setSelection() triggers a createMainFragment(),
+            // should not create fragment.
 
             mSelectedPosition = savedInstanceState != null
                     ? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
 
-            if (!mIsPageRow) {
-                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
-                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
-                            .getMainFragmentRowsAdapter();
-                } else {
-                    mMainFragmentRowsAdapter = null;
-                }
-            } else {
-                mMainFragmentRowsAdapter = null;
-            }
+            setMainFragmentAdapter();
         }
 
         mHeadersFragment.setHeadersGone(!mCanShowHeaders);
@@ -1242,8 +1287,6 @@
         mScaleFrameLayout.setPivotX(0);
         mScaleFrameLayout.setPivotY(mContainerListAlignTop);
 
-        setupMainFragment();
-
         if (mBrandColorSet) {
             mHeadersFragment.setBackgroundColor(mBrandColor);
         }
@@ -1270,17 +1313,6 @@
         return root;
     }
 
-    private void setupMainFragment() {
-        if (mMainFragmentRowsAdapter != null) {
-            if (mAdapter != null) {
-                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
-            }
-            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
-                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
-            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
-        }
-    }
-
     void createHeadersTransition() {
         mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
                 mShowingHeaders
@@ -1470,10 +1502,10 @@
     };
 
     void onRowSelected(int position) {
-        if (position != mSelectedPosition) {
-            mSetSelectionRunnable.post(
-                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
-        }
+        // even position is same, it could be data changed, always post selection runnable
+        // to possibly swap main fragment.
+        mSetSelectionRunnable.post(
+                position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
     }
 
     void setSelection(int position, boolean smooth) {
@@ -1500,7 +1532,6 @@
         if (createMainFragment(mAdapter, position)) {
             swapToMainFragment();
             expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
-            setupMainFragment();
         }
     }
 
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 4a2502a..c28064c 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -567,14 +567,27 @@
         }
 
         boolean oldIsPageRow = mIsPageRow;
+        Object oldPageRow = mPageRow;
         mIsPageRow = mCanShowHeaders && item instanceof PageRow;
+        mPageRow = mIsPageRow ? item : null;
         boolean swap;
 
         if (mMainFragment == null) {
             swap = true;
         } else {
             if (oldIsPageRow) {
-                swap = true;
+                if (mIsPageRow) {
+                    if (oldPageRow == null) {
+                        // fragment is restored, page row object not yet set, so just set the
+                        // mPageRow object and there is no need to replace the fragment
+                        swap = false;
+                    } else {
+                        // swap if page row object changes
+                        swap = oldPageRow != mPageRow;
+                    }
+                } else {
+                    swap = true;
+                }
             } else {
                 swap = mIsPageRow;
             }
@@ -587,25 +600,29 @@
                         "Fragment must implement MainFragmentAdapterProvider");
             }
 
-            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
-                    .getMainFragmentAdapter();
-            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
-            if (!mIsPageRow) {
-                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
-                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
-                            .getMainFragmentRowsAdapter();
-                } else {
-                    mMainFragmentRowsAdapter = null;
-                }
-                mIsPageRow = mMainFragmentRowsAdapter == null;
-            } else {
-                mMainFragmentRowsAdapter = null;
-            }
+            setMainFragmentAdapter();
         }
 
         return swap;
     }
 
+    void setMainFragmentAdapter() {
+        mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment)
+                .getMainFragmentAdapter();
+        mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+        if (!mIsPageRow) {
+            if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+                setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment)
+                        .getMainFragmentRowsAdapter());
+            } else {
+                setMainFragmentRowsAdapter(null);
+            }
+            mIsPageRow = mMainFragmentRowsAdapter == null;
+        } else {
+            setMainFragmentRowsAdapter(null);
+        }
+    }
+
     /**
      * Factory class responsible for creating fragment given the current item. {@link ListRow}
      * should return {@link RowsSupportFragment} or its subclass whereas {@link PageRow}
@@ -675,7 +692,8 @@
     MainFragmentAdapter mMainFragmentAdapter;
     Fragment mMainFragment;
     HeadersSupportFragment mHeadersSupportFragment;
-    private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+    ListRowDataAdapter mMainFragmentListRowDataAdapter;
 
     private ObjectAdapter mAdapter;
     private PresenterSelector mAdapterPresenter;
@@ -698,6 +716,7 @@
     private int mSelectedPosition = -1;
     private float mScaleFactor;
     boolean mIsPageRow;
+    Object mPageRow;
 
     private PresenterSelector mHeaderPresenterSelector;
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -817,11 +836,45 @@
             return;
         }
 
-        if (mMainFragmentRowsAdapter != null) {
-            mMainFragmentRowsAdapter.setAdapter(
-                    adapter == null ? null : new ListRowDataAdapter(adapter));
+        updateMainFragmentRowsAdapter();
+        mHeadersSupportFragment.setAdapter(mAdapter);
+    }
+
+    void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+        if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+            return;
         }
-        mHeadersSupportFragment.setAdapter(adapter);
+        // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter
+        if (mMainFragmentRowsAdapter != null) {
+            // RowsFragment cannot change click/select listeners after view created.
+            // The main fragment and adapter should be GCed as long as there is no reference from
+            // BrowseSupportFragment to it.
+            mMainFragmentRowsAdapter.setAdapter(null);
+        }
+        mMainFragmentRowsAdapter = mainFragmentRowsAdapter;
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+        // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter
+        updateMainFragmentRowsAdapter();
+    }
+
+    /**
+     * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter.
+     * It also clears old mMainFragmentListRowDataAdapter.
+     */
+    void updateMainFragmentRowsAdapter() {
+        if (mMainFragmentListRowDataAdapter != null) {
+            mMainFragmentListRowDataAdapter.detach();
+            mMainFragmentListRowDataAdapter = null;
+        }
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentListRowDataAdapter = mAdapter == null
+                    ? null : new ListRowDataAdapter(mAdapter);
+            mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter);
+        }
     }
 
     public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
@@ -1141,7 +1194,8 @@
 
     @Override
     public void onDestroyView() {
-        mMainFragmentRowsAdapter = null;
+        setMainFragmentRowsAdapter(null);
+        mPageRow = null;
         mMainFragmentAdapter = null;
         mMainFragment = null;
         mHeadersSupportFragment = null;
@@ -1195,26 +1249,17 @@
             mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
                     .findFragmentById(R.id.browse_headers_dock);
             mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
-            mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
-                    .getMainFragmentAdapter();
-            mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
 
             mIsPageRow = savedInstanceState != null
                     && savedInstanceState.getBoolean(IS_PAGE_ROW, false);
+            // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is
+            // the case for restoring, later if setSelection() triggers a createMainFragment(),
+            // should not create fragment.
 
             mSelectedPosition = savedInstanceState != null
                     ? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
 
-            if (!mIsPageRow) {
-                if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
-                    mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
-                            .getMainFragmentRowsAdapter();
-                } else {
-                    mMainFragmentRowsAdapter = null;
-                }
-            } else {
-                mMainFragmentRowsAdapter = null;
-            }
+            setMainFragmentAdapter();
         }
 
         mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
@@ -1239,8 +1284,6 @@
         mScaleFrameLayout.setPivotX(0);
         mScaleFrameLayout.setPivotY(mContainerListAlignTop);
 
-        setupMainFragment();
-
         if (mBrandColorSet) {
             mHeadersSupportFragment.setBackgroundColor(mBrandColor);
         }
@@ -1267,17 +1310,6 @@
         return root;
     }
 
-    private void setupMainFragment() {
-        if (mMainFragmentRowsAdapter != null) {
-            if (mAdapter != null) {
-                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
-            }
-            mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
-                    new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
-            mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
-        }
-    }
-
     void createHeadersTransition() {
         mHeadersTransition = TransitionHelper.loadTransition(getContext(),
                 mShowingHeaders
@@ -1467,10 +1499,10 @@
     };
 
     void onRowSelected(int position) {
-        if (position != mSelectedPosition) {
-            mSetSelectionRunnable.post(
-                    position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
-        }
+        // even position is same, it could be data changed, always post selection runnable
+        // to possibly swap main fragment.
+        mSetSelectionRunnable.post(
+                position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
     }
 
     void setSelection(int position, boolean smooth) {
@@ -1497,7 +1529,6 @@
         if (createMainFragment(mAdapter, position)) {
             swapToMainFragment();
             expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
-            setupMainFragment();
         }
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
index f9af12f..03d948b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
@@ -13,6 +13,7 @@
  * thinks there are items even though they're invisible. This class takes care of filtering out
  * the invisible rows at the end. In case the data inside the adapter changes, it adjusts the
  * bounds to reflect the latest data.
+ * {@link #detach()} must be called to release DataObserver from Adapter.
  */
 class ListRowDataAdapter extends ObjectAdapter {
     public static final int ON_ITEM_RANGE_CHANGED = 2;
@@ -22,6 +23,7 @@
 
     private final ObjectAdapter mAdapter;
     int mLastVisibleRowIndex;
+    final DataObserver mDataObserver;
 
     public ListRowDataAdapter(ObjectAdapter adapter) {
         super(adapter.getPresenterSelector());
@@ -34,10 +36,20 @@
         // operation. To handle this case, we use QueueBasedDataObserver which forces
         // recyclerview to do a full data refresh after each update operation.
         if (adapter.isImmediateNotifySupported()) {
-            mAdapter.registerObserver(new SimpleDataObserver());
+            mDataObserver = new SimpleDataObserver();
         } else {
-            mAdapter.registerObserver(new QueueBasedDataObserver());
+            mDataObserver = new QueueBasedDataObserver();
         }
+        attach();
+    }
+
+    void detach() {
+        mAdapter.unregisterObserver(mDataObserver);
+    }
+
+    void attach() {
+        initialize();
+        mAdapter.registerObserver(mDataObserver);
     }
 
     void initialize() {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
index 535f81b..d411f9e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
@@ -13,7 +13,10 @@
  */
 package android.support.v17.leanback.widget;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
 import android.database.Observable;
+import android.support.annotation.RestrictTo;
 
 /**
  * Base class adapter to be used in leanback activities.  Provides access to a data model and is
@@ -132,6 +135,10 @@
                 mObservers.get(i).onItemMoved(positionStart, toPosition);
             }
         }
+
+        boolean hasObserver() {
+            return mObservers.size() > 0;
+        }
     }
 
     private final DataObservable mObservable = new DataObservable();
@@ -207,6 +214,14 @@
     }
 
     /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public final boolean hasObserver() {
+        return mObservable.hasObserver();
+    }
+
+    /**
      * Unregisters all DataObservers for this ObjectAdapter.
      */
     public final void unregisterAllObservers() {
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
index 08e38ca..b29153a 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
@@ -16,6 +16,8 @@
 package android.support.v17.leanback.app;
 
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertNull;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -86,6 +88,49 @@
         }
     }
 
+    static Bundle saveActivityState(final SingleFragmentTestActivity activity) {
+        final Bundle[] savedState = new Bundle[1];
+        // save activity state
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                savedState[0] = activity.performSaveInstanceState();
+            }
+        });
+        return savedState[0];
+    }
+
+    static void waitForHeaderTransition(final F_Base fragment) {
+        // Wait header transition finishes
+        SystemClock.sleep(100);
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return !fragment.isInHeadersTransition();
+            }
+        });
+    }
+
+    static void selectAndWaitFragmentAnimation(final F_Base fragment, final int row,
+            final int item) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setSelectedPosition(row, true,
+                        new ListRowPresenter.SelectItemViewHolderTask(item));
+            }
+        });
+        // Wait header transition finishes and scrolling stops
+        SystemClock.sleep(100);
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return !fragment.isInHeadersTransition()
+                        && !fragment.getHeadersFragment().isScrolling();
+            }
+        });
+    }
+
     public static class F_defaultAlignment extends RowsFragment {
         @Override
         public void onCreate(Bundle savedInstanceState) {
@@ -497,6 +542,48 @@
             assertEquals(0, mEntranceTransitionStartTS.size());
             assertEquals(0, mEntranceTransitionEndTS.size());
         }
+
+        /**
+         * Util to wait PageFragment swapped.
+         */
+        Fragment waitPageFragment(final Class pageFragmentClass) {
+            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+                @Override
+                public boolean canProceed() {
+                    return pageFragmentClass.isInstance(getMainFragment())
+                            && getMainFragment().getView() != null;
+                }
+            });
+            return getMainFragment();
+        }
+
+        /**
+         * Wait until a fragment for non-page Row is created. Does not apply to the case a
+         * RowsFragment is created on a PageRow.
+         */
+        RowsFragment waitRowsFragment() {
+            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+                @Override
+                public boolean canProceed() {
+                    return mMainFragmentListRowDataAdapter != null
+                            && getMainFragment() instanceof RowsFragment
+                            && !(getMainFragment() instanceof SampleRowsFragment);
+                }
+            });
+            return (RowsFragment) getMainFragment();
+        }
+    }
+
+    static ObjectAdapter createListRowAdapter() {
+        StableIdAdapter listRowAdapter = new StableIdAdapter();
+        listRowAdapter.setHasStableIds(false);
+        listRowAdapter.setPresenterSelector(
+                new SinglePresenterSelector(sCardPresenter));
+        int index = 0;
+        listRowAdapter.mList.add(index++);
+        listRowAdapter.mList.add(index++);
+        listRowAdapter.mList.add(index++);
+        return listRowAdapter;
     }
 
     /**
@@ -506,14 +593,7 @@
         ListRowPresenter lrp = new ListRowPresenter();
         final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
         for (int i = 0; i < 3; i++) {
-            StableIdAdapter listRowAdapter = new StableIdAdapter();
-            listRowAdapter.setHasStableIds(false);
-            listRowAdapter.setPresenterSelector(
-                    new SinglePresenterSelector(sCardPresenter));
-            int index = 0;
-            listRowAdapter.mList.add(index++);
-            listRowAdapter.mList.add(index++);
-            listRowAdapter.mList.add(index++);
+            ObjectAdapter listRowAdapter = createListRowAdapter();
             HeaderItem header = new HeaderItem(i, "Row " + i);
             adapter.add(new ListRow(header, listRowAdapter));
         }
@@ -548,19 +628,250 @@
         final F_standard fragment = ((F_standard) activity.getTestFragment());
         fragment.assertExecutedEntranceTransition();
 
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 fragment.setAdapter(null);
             }
         });
+        // adapter should no longer has observer and there is no reference to adapter from
+        // BrowseFragment.
+        assertFalse(adapter1.hasObserver());
+        assertFalse(wrappedAdapter.hasObserver());
+        assertNull(fragment.getAdapter());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        // RowsFragment is still there
+        assertTrue(fragment.mMainFragment instanceof RowsFragment);
+        assertNotNull(fragment.mMainFragmentRowsAdapter);
+        assertNotNull(fragment.mMainFragmentAdapter);
 
+        // initialize to same adapter
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                fragment.setAdapter(createListRowsAdapter());
+                fragment.setAdapter(adapter1);
             }
         });
+        assertTrue(adapter1.hasObserver());
+        assertNotSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentChangeAdapter() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+        final ObjectAdapter adapter2 = createListRowsAdapter();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setAdapter(adapter2);
+            }
+        });
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertFalse(adapter1.hasObserver());
+        assertFalse(wrappedAdapter.hasObserver());
+        assertSame(adapter2, fragment.getAdapter());
+        assertNotSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter2.hasObserver());
+        assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentChangeAdapterToPage() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+        final ObjectAdapter adapter2 = create2PageRow3ListRow();
+        fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
+                new MyFragmentFactory());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setAdapter(adapter2);
+            }
+        });
+        fragment.waitPageFragment(SampleRowsFragment.class);
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertFalse(adapter1.hasObserver());
+        assertFalse(wrappedAdapter.hasObserver());
+        assertSame(adapter2, fragment.getAdapter());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter2.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyDataChangeListRowToPage() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+
+        fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
+                new MyFragmentFactory());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                adapter1.replace(0, new MyPageRow(0));
+            }
+        });
+        fragment.waitPageFragment(SampleRowsFragment.class);
+        assertTrue(adapter1.hasObserver());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyDataChangeListRowToListRow() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+
+        fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
+                new MyFragmentFactory());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                ObjectAdapter listRowAdapter = createListRowAdapter();
+                HeaderItem header = new HeaderItem(0, "Row 0 changed");
+                adapter1.replace(0, new ListRow(header, listRowAdapter));
+            }
+        });
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+        assertSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentChangeAdapterPageToPage() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
+        final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter1.hasObserver());
+        final ObjectAdapter adapter2 = create2PageRow3ListRow();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setAdapter(adapter2);
+            }
+        });
+        fragment.waitPageFragment(SampleRowsFragment.class);
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertFalse(adapter1.hasObserver());
+        assertSame(adapter2, fragment.getAdapter());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter2.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyChangePageToPage() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
+        final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter1.hasObserver());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                adapter1.removeItems(0, 1);
+                adapter1.add(0, new MyPageRow(1));
+            }
+        });
+        fragment.waitPageFragment(SampleFragment.class);
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertTrue(adapter1.hasObserver());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+    }
+
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentChangeAdapterPageToListRow() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
+        final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter1.hasObserver());
+        final ObjectAdapter adapter2 = createListRowsAdapter();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setAdapter(adapter2);
+            }
+        });
+        fragment.waitRowsFragment();
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertFalse(adapter1.hasObserver());
+        assertSame(adapter2, fragment.getAdapter());
+        assertTrue(adapter2.hasObserver());
+        assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyDataChangePageToListRow() throws InterruptedException {
+        final SingleFragmentTestActivity activity = launchAndWaitActivity(
+                RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
+        final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter1.hasObserver());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                ObjectAdapter listRowAdapter = createListRowAdapter();
+                HeaderItem header = new HeaderItem(0, "Row 0 changed");
+                adapter1.removeItems(0, 1);
+                adapter1.add(0, new ListRow(header, listRowAdapter));
+            }
+        });
+        fragment.waitRowsFragment();
+        assertTrue(adapter1.hasObserver());
+        assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
     }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
@@ -572,27 +883,15 @@
         fragment.assertExecutedEntranceTransition();
 
         // select item 2 on row 1
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                fragment.setSelectedPosition(1, true,
-                        new ListRowPresenter.SelectItemViewHolderTask(2));
-            }
-        });
+        selectAndWaitFragmentAnimation(fragment, 1, 2);
         // save activity to state
-        final Bundle[] savedState = new Bundle[1];
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                savedState[0] = activity.performSaveInstanceState();
-            }
-        });
+        Bundle savedState = saveActivityState(activity);
         activity.finish();
 
         // recreate activity with saved state
         SingleFragmentTestActivity activity2 = launchAndWaitActivity(
                 RowsFragmentTest.F_standard.class,
-                new Options().savedInstance(savedState[0]), 2000);
+                new Options().savedInstance(savedState), 2000);
         final F_standard fragment2 = ((F_standard) activity2.getTestFragment());
         // validate restored activity selected row and selected item
         fragment2.assertNoEntranceTransition();
@@ -703,23 +1002,47 @@
     }
 
     /**
+     * Create BrowseFragmentAdapter with 2 PageRows then 3 ListRow
+     */
+    private static ArrayObjectAdapter create2PageRow3ListRow() {
+        ListRowPresenter lrp = new ListRowPresenter();
+        final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+        adapter.add(new MyPageRow(0));
+        adapter.add(new MyPageRow(1));
+        for (int i = 0; i < 3; i++) {
+            StableIdAdapter listRowAdapter = new StableIdAdapter();
+            listRowAdapter.setHasStableIds(false);
+            listRowAdapter.setPresenterSelector(
+                    new SinglePresenterSelector(sCardPresenter));
+            int index = 0;
+            listRowAdapter.mList.add(index++);
+            listRowAdapter.mList.add(index++);
+            listRowAdapter.mList.add(index++);
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+        return adapter;
+    }
+
+    static class MyFragmentFactory extends BrowseFragment.FragmentFactory {
+        @Override
+        public Fragment createFragment(Object rowObj) {
+            MyPageRow row = (MyPageRow) rowObj;
+            if (row.type == 0) {
+                return new SampleRowsFragment();
+            } else if (row.type == 1) {
+                return new SampleFragment();
+            }
+            return null;
+        }
+    }
+
+    /**
      * A BrowseFragment with three ListRows, one SampleRowsFragment and one SampleFragment.
      */
     public static class F_3ListRow2PageRow extends F_Base {
         public F_3ListRow2PageRow() {
-            getMainFragmentRegistry().registerFragment(MyPageRow.class,
-                    new BrowseFragment.FragmentFactory() {
-                        @Override
-                        public Fragment createFragment(Object rowObj) {
-                            MyPageRow row = (MyPageRow) rowObj;
-                            if (row.type == 0) {
-                                return new SampleRowsFragment();
-                            } else if (row.type == 1) {
-                                return new SampleFragment();
-                            }
-                            return null;
-                        }
-                });
+            getMainFragmentRegistry().registerFragment(MyPageRow.class, new MyFragmentFactory());
         }
         @Override
         public void onCreate(Bundle savedInstanceState) {
@@ -735,19 +1058,28 @@
                 }
             }, 100);
         }
+    }
 
-        /**
-         * Util to wait PageFragment swapped.
-         */
-        Fragment waitPageFragment(final Class pageFragmentClass) {
-            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+    /**
+     * A BrowseFragment with three ListRows, one SampleRowsFragment and one SampleFragment.
+     */
+    public static class F_2PageRow3ListRow extends F_Base {
+        public F_2PageRow3ListRow() {
+            getMainFragmentRegistry().registerFragment(MyPageRow.class, new MyFragmentFactory());
+        }
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+            new Handler().postDelayed(new Runnable() {
                 @Override
-                public boolean canProceed() {
-                    return pageFragmentClass.isInstance(getMainFragment())
-                            && getMainFragment().getView() != null;
+                public void run() {
+                    setAdapter(create2PageRow3ListRow());
+                    startEntranceTransition();
                 }
-            });
-            return getMainFragment();
+            }, 100);
         }
     }
 
@@ -760,27 +1092,14 @@
         fragment.assertExecutedEntranceTransition();
 
         // select item 2 on row 1.
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                fragment.setSelectedPosition(1, true,
-                        new ListRowPresenter.SelectItemViewHolderTask(2));
-            }
-        });
-        final Bundle[] savedState = new Bundle[1];
-        // save activity state
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                savedState[0] = activity.performSaveInstanceState();
-            }
-        });
+        selectAndWaitFragmentAnimation(fragment, 1, 2);
+        Bundle savedState = saveActivityState(activity);
         activity.finish();
 
         // start a new activity with the state
         SingleFragmentTestActivity activity2 = launchAndWaitActivity(
                 RowsFragmentTest.F_standard.class,
-                new Options().savedInstance(savedState[0]), 2000);
+                new Options().savedInstance(savedState), 2000);
         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
         assertFalse(fragment2.isShowingHeaders());
         fragment2.assertNoEntranceTransition();
@@ -816,13 +1135,7 @@
             }
         });
         // Wait header transition finishes
-        SystemClock.sleep(100);
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return !fragment.isInHeadersTransition();
-            }
-        });
+        waitForHeaderTransition(fragment);
         // Select item 1 on row 1 in SampleRowsFragment
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
@@ -832,18 +1145,12 @@
             }
         });
         // Save activity state
-        final Bundle[] savedState = new Bundle[1];
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                savedState[0] = activity.performSaveInstanceState();
-            }
-        });
+        Bundle savedState = saveActivityState(activity);
         activity.finish();
 
         SingleFragmentTestActivity activity2 = launchAndWaitActivity(
                 RowsFragmentTest.F_3ListRow2PageRow.class,
-                new Options().savedInstance(savedState[0]), 2000);
+                new Options().savedInstance(savedState), 2000);
         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
         final SampleRowsFragment mainFragment2 = (SampleRowsFragment) fragment2.waitPageFragment(
                 SampleRowsFragment.class);
@@ -896,14 +1203,7 @@
                 }
             }
         });
-        SystemClock.sleep(100);
-        // Wait header transition finishes
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return !fragment.isInHeadersTransition();
-            }
-        });
+        waitForHeaderTransition(fragment);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
@@ -913,18 +1213,12 @@
             }
         });
         // Save activity state
-        final Bundle[] savedState = new Bundle[1];
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                savedState[0] = activity.performSaveInstanceState();
-            }
-        });
+        Bundle savedState = saveActivityState(activity);
         activity.finish();
 
         SingleFragmentTestActivity activity2 = launchAndWaitActivity(
                 RowsFragmentTest.F_3ListRow2PageRow.class,
-                new Options().savedInstance(savedState[0]), 2000);
+                new Options().savedInstance(savedState), 2000);
         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
         final SampleFragment mainFragment2 = (SampleFragment) fragment2.waitPageFragment(
                 SampleFragment.class);
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
index a186938..b9da794 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
@@ -19,6 +19,8 @@
 package android.support.v17.leanback.app;
 
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertNull;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -89,6 +91,49 @@
         }
     }
 
+    static Bundle saveActivityState(final SingleSupportFragmentTestActivity activity) {
+        final Bundle[] savedState = new Bundle[1];
+        // save activity state
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                savedState[0] = activity.performSaveInstanceState();
+            }
+        });
+        return savedState[0];
+    }
+
+    static void waitForHeaderTransition(final F_Base fragment) {
+        // Wait header transition finishes
+        SystemClock.sleep(100);
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return !fragment.isInHeadersTransition();
+            }
+        });
+    }
+
+    static void selectAndWaitFragmentAnimation(final F_Base fragment, final int row,
+            final int item) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setSelectedPosition(row, true,
+                        new ListRowPresenter.SelectItemViewHolderTask(item));
+            }
+        });
+        // Wait header transition finishes and scrolling stops
+        SystemClock.sleep(100);
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return !fragment.isInHeadersTransition()
+                        && !fragment.getHeadersSupportFragment().isScrolling();
+            }
+        });
+    }
+
     public static class F_defaultAlignment extends RowsSupportFragment {
         @Override
         public void onCreate(Bundle savedInstanceState) {
@@ -500,6 +545,48 @@
             assertEquals(0, mEntranceTransitionStartTS.size());
             assertEquals(0, mEntranceTransitionEndTS.size());
         }
+
+        /**
+         * Util to wait PageFragment swapped.
+         */
+        Fragment waitPageFragment(final Class pageFragmentClass) {
+            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+                @Override
+                public boolean canProceed() {
+                    return pageFragmentClass.isInstance(getMainFragment())
+                            && getMainFragment().getView() != null;
+                }
+            });
+            return getMainFragment();
+        }
+
+        /**
+         * Wait until a fragment for non-page Row is created. Does not apply to the case a
+         * RowsSupportFragment is created on a PageRow.
+         */
+        RowsSupportFragment waitRowsSupportFragment() {
+            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+                @Override
+                public boolean canProceed() {
+                    return mMainFragmentListRowDataAdapter != null
+                            && getMainFragment() instanceof RowsSupportFragment
+                            && !(getMainFragment() instanceof SampleRowsSupportFragment);
+                }
+            });
+            return (RowsSupportFragment) getMainFragment();
+        }
+    }
+
+    static ObjectAdapter createListRowAdapter() {
+        StableIdAdapter listRowAdapter = new StableIdAdapter();
+        listRowAdapter.setHasStableIds(false);
+        listRowAdapter.setPresenterSelector(
+                new SinglePresenterSelector(sCardPresenter));
+        int index = 0;
+        listRowAdapter.mList.add(index++);
+        listRowAdapter.mList.add(index++);
+        listRowAdapter.mList.add(index++);
+        return listRowAdapter;
     }
 
     /**
@@ -509,14 +596,7 @@
         ListRowPresenter lrp = new ListRowPresenter();
         final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
         for (int i = 0; i < 3; i++) {
-            StableIdAdapter listRowAdapter = new StableIdAdapter();
-            listRowAdapter.setHasStableIds(false);
-            listRowAdapter.setPresenterSelector(
-                    new SinglePresenterSelector(sCardPresenter));
-            int index = 0;
-            listRowAdapter.mList.add(index++);
-            listRowAdapter.mList.add(index++);
-            listRowAdapter.mList.add(index++);
+            ObjectAdapter listRowAdapter = createListRowAdapter();
             HeaderItem header = new HeaderItem(i, "Row " + i);
             adapter.add(new ListRow(header, listRowAdapter));
         }
@@ -551,19 +631,250 @@
         final F_standard fragment = ((F_standard) activity.getTestFragment());
         fragment.assertExecutedEntranceTransition();
 
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 fragment.setAdapter(null);
             }
         });
+        // adapter should no longer has observer and there is no reference to adapter from
+        // BrowseSupportFragment.
+        assertFalse(adapter1.hasObserver());
+        assertFalse(wrappedAdapter.hasObserver());
+        assertNull(fragment.getAdapter());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        // RowsSupportFragment is still there
+        assertTrue(fragment.mMainFragment instanceof RowsSupportFragment);
+        assertNotNull(fragment.mMainFragmentRowsAdapter);
+        assertNotNull(fragment.mMainFragmentAdapter);
 
+        // initialize to same adapter
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                fragment.setAdapter(createListRowsAdapter());
+                fragment.setAdapter(adapter1);
             }
         });
+        assertTrue(adapter1.hasObserver());
+        assertNotSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentChangeAdapter() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+        final ObjectAdapter adapter2 = createListRowsAdapter();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setAdapter(adapter2);
+            }
+        });
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertFalse(adapter1.hasObserver());
+        assertFalse(wrappedAdapter.hasObserver());
+        assertSame(adapter2, fragment.getAdapter());
+        assertNotSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter2.hasObserver());
+        assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentChangeAdapterToPage() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+        final ObjectAdapter adapter2 = create2PageRow3ListRow();
+        fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
+                new MyFragmentFactory());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setAdapter(adapter2);
+            }
+        });
+        fragment.waitPageFragment(SampleRowsSupportFragment.class);
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertFalse(adapter1.hasObserver());
+        assertFalse(wrappedAdapter.hasObserver());
+        assertSame(adapter2, fragment.getAdapter());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter2.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyDataChangeListRowToPage() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+
+        fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
+                new MyFragmentFactory());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                adapter1.replace(0, new MyPageRow(0));
+            }
+        });
+        fragment.waitPageFragment(SampleRowsSupportFragment.class);
+        assertTrue(adapter1.hasObserver());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyDataChangeListRowToListRow() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_standard.class, 2000);
+        final F_standard fragment = ((F_standard) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
+        ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+
+        fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
+                new MyFragmentFactory());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                ObjectAdapter listRowAdapter = createListRowAdapter();
+                HeaderItem header = new HeaderItem(0, "Row 0 changed");
+                adapter1.replace(0, new ListRow(header, listRowAdapter));
+            }
+        });
+        assertTrue(adapter1.hasObserver());
+        assertTrue(wrappedAdapter.hasObserver());
+        assertSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentChangeAdapterPageToPage() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_2PageRow3ListRow.class, 2000);
+        final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter1.hasObserver());
+        final ObjectAdapter adapter2 = create2PageRow3ListRow();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setAdapter(adapter2);
+            }
+        });
+        fragment.waitPageFragment(SampleRowsSupportFragment.class);
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertFalse(adapter1.hasObserver());
+        assertSame(adapter2, fragment.getAdapter());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter2.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyChangePageToPage() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_2PageRow3ListRow.class, 2000);
+        final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter1.hasObserver());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                adapter1.removeItems(0, 1);
+                adapter1.add(0, new MyPageRow(1));
+            }
+        });
+        fragment.waitPageFragment(SampleFragment.class);
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertTrue(adapter1.hasObserver());
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+    }
+
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentChangeAdapterPageToListRow() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_2PageRow3ListRow.class, 2000);
+        final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ObjectAdapter adapter1 = fragment.getAdapter();
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter1.hasObserver());
+        final ObjectAdapter adapter2 = createListRowsAdapter();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragment.setAdapter(adapter2);
+            }
+        });
+        fragment.waitRowsSupportFragment();
+        // adapter1 should no longer has observer and adapter2 will have observer
+        assertFalse(adapter1.hasObserver());
+        assertSame(adapter2, fragment.getAdapter());
+        assertTrue(adapter2.hasObserver());
+        assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+    @Test
+    public void browseFragmentNotifyDataChangePageToListRow() throws InterruptedException {
+        final SingleSupportFragmentTestActivity activity = launchAndWaitActivity(
+                RowsSupportFragmentTest.F_2PageRow3ListRow.class, 2000);
+        final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
+        fragment.assertExecutedEntranceTransition();
+
+        final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
+        assertNull(fragment.mMainFragmentListRowDataAdapter);
+        assertTrue(adapter1.hasObserver());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                ObjectAdapter listRowAdapter = createListRowAdapter();
+                HeaderItem header = new HeaderItem(0, "Row 0 changed");
+                adapter1.removeItems(0, 1);
+                adapter1.add(0, new ListRow(header, listRowAdapter));
+            }
+        });
+        fragment.waitRowsSupportFragment();
+        assertTrue(adapter1.hasObserver());
+        assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
     }
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
@@ -575,27 +886,15 @@
         fragment.assertExecutedEntranceTransition();
 
         // select item 2 on row 1
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                fragment.setSelectedPosition(1, true,
-                        new ListRowPresenter.SelectItemViewHolderTask(2));
-            }
-        });
+        selectAndWaitFragmentAnimation(fragment, 1, 2);
         // save activity to state
-        final Bundle[] savedState = new Bundle[1];
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                savedState[0] = activity.performSaveInstanceState();
-            }
-        });
+        Bundle savedState = saveActivityState(activity);
         activity.finish();
 
         // recreate activity with saved state
         SingleSupportFragmentTestActivity activity2 = launchAndWaitActivity(
                 RowsSupportFragmentTest.F_standard.class,
-                new Options().savedInstance(savedState[0]), 2000);
+                new Options().savedInstance(savedState), 2000);
         final F_standard fragment2 = ((F_standard) activity2.getTestFragment());
         // validate restored activity selected row and selected item
         fragment2.assertNoEntranceTransition();
@@ -706,23 +1005,47 @@
     }
 
     /**
+     * Create BrowseSupportFragmentAdapter with 2 PageRows then 3 ListRow
+     */
+    private static ArrayObjectAdapter create2PageRow3ListRow() {
+        ListRowPresenter lrp = new ListRowPresenter();
+        final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
+        adapter.add(new MyPageRow(0));
+        adapter.add(new MyPageRow(1));
+        for (int i = 0; i < 3; i++) {
+            StableIdAdapter listRowAdapter = new StableIdAdapter();
+            listRowAdapter.setHasStableIds(false);
+            listRowAdapter.setPresenterSelector(
+                    new SinglePresenterSelector(sCardPresenter));
+            int index = 0;
+            listRowAdapter.mList.add(index++);
+            listRowAdapter.mList.add(index++);
+            listRowAdapter.mList.add(index++);
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            adapter.add(new ListRow(header, listRowAdapter));
+        }
+        return adapter;
+    }
+
+    static class MyFragmentFactory extends BrowseSupportFragment.FragmentFactory {
+        @Override
+        public Fragment createFragment(Object rowObj) {
+            MyPageRow row = (MyPageRow) rowObj;
+            if (row.type == 0) {
+                return new SampleRowsSupportFragment();
+            } else if (row.type == 1) {
+                return new SampleFragment();
+            }
+            return null;
+        }
+    }
+
+    /**
      * A BrowseSupportFragment with three ListRows, one SampleRowsSupportFragment and one SampleFragment.
      */
     public static class F_3ListRow2PageRow extends F_Base {
         public F_3ListRow2PageRow() {
-            getMainFragmentRegistry().registerFragment(MyPageRow.class,
-                    new BrowseSupportFragment.FragmentFactory() {
-                        @Override
-                        public Fragment createFragment(Object rowObj) {
-                            MyPageRow row = (MyPageRow) rowObj;
-                            if (row.type == 0) {
-                                return new SampleRowsSupportFragment();
-                            } else if (row.type == 1) {
-                                return new SampleFragment();
-                            }
-                            return null;
-                        }
-                });
+            getMainFragmentRegistry().registerFragment(MyPageRow.class, new MyFragmentFactory());
         }
         @Override
         public void onCreate(Bundle savedInstanceState) {
@@ -738,19 +1061,28 @@
                 }
             }, 100);
         }
+    }
 
-        /**
-         * Util to wait PageFragment swapped.
-         */
-        Fragment waitPageFragment(final Class pageFragmentClass) {
-            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+    /**
+     * A BrowseSupportFragment with three ListRows, one SampleRowsSupportFragment and one SampleFragment.
+     */
+    public static class F_2PageRow3ListRow extends F_Base {
+        public F_2PageRow3ListRow() {
+            getMainFragmentRegistry().registerFragment(MyPageRow.class, new MyFragmentFactory());
+        }
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (savedInstanceState == null) {
+                prepareEntranceTransition();
+            }
+            new Handler().postDelayed(new Runnable() {
                 @Override
-                public boolean canProceed() {
-                    return pageFragmentClass.isInstance(getMainFragment())
-                            && getMainFragment().getView() != null;
+                public void run() {
+                    setAdapter(create2PageRow3ListRow());
+                    startEntranceTransition();
                 }
-            });
-            return getMainFragment();
+            }, 100);
         }
     }
 
@@ -763,27 +1095,14 @@
         fragment.assertExecutedEntranceTransition();
 
         // select item 2 on row 1.
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                fragment.setSelectedPosition(1, true,
-                        new ListRowPresenter.SelectItemViewHolderTask(2));
-            }
-        });
-        final Bundle[] savedState = new Bundle[1];
-        // save activity state
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                savedState[0] = activity.performSaveInstanceState();
-            }
-        });
+        selectAndWaitFragmentAnimation(fragment, 1, 2);
+        Bundle savedState = saveActivityState(activity);
         activity.finish();
 
         // start a new activity with the state
         SingleSupportFragmentTestActivity activity2 = launchAndWaitActivity(
                 RowsSupportFragmentTest.F_standard.class,
-                new Options().savedInstance(savedState[0]), 2000);
+                new Options().savedInstance(savedState), 2000);
         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
         assertFalse(fragment2.isShowingHeaders());
         fragment2.assertNoEntranceTransition();
@@ -819,13 +1138,7 @@
             }
         });
         // Wait header transition finishes
-        SystemClock.sleep(100);
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return !fragment.isInHeadersTransition();
-            }
-        });
+        waitForHeaderTransition(fragment);
         // Select item 1 on row 1 in SampleRowsSupportFragment
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
@@ -835,18 +1148,12 @@
             }
         });
         // Save activity state
-        final Bundle[] savedState = new Bundle[1];
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                savedState[0] = activity.performSaveInstanceState();
-            }
-        });
+        Bundle savedState = saveActivityState(activity);
         activity.finish();
 
         SingleSupportFragmentTestActivity activity2 = launchAndWaitActivity(
                 RowsSupportFragmentTest.F_3ListRow2PageRow.class,
-                new Options().savedInstance(savedState[0]), 2000);
+                new Options().savedInstance(savedState), 2000);
         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
         final SampleRowsSupportFragment mainFragment2 = (SampleRowsSupportFragment) fragment2.waitPageFragment(
                 SampleRowsSupportFragment.class);
@@ -899,14 +1206,7 @@
                 }
             }
         });
-        SystemClock.sleep(100);
-        // Wait header transition finishes
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return !fragment.isInHeadersTransition();
-            }
-        });
+        waitForHeaderTransition(fragment);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
@@ -916,18 +1216,12 @@
             }
         });
         // Save activity state
-        final Bundle[] savedState = new Bundle[1];
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                savedState[0] = activity.performSaveInstanceState();
-            }
-        });
+        Bundle savedState = saveActivityState(activity);
         activity.finish();
 
         SingleSupportFragmentTestActivity activity2 = launchAndWaitActivity(
                 RowsSupportFragmentTest.F_3ListRow2PageRow.class,
-                new Options().savedInstance(savedState[0]), 2000);
+                new Options().savedInstance(savedState), 2000);
         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
         final SampleFragment mainFragment2 = (SampleFragment) fragment2.waitPageFragment(
                 SampleFragment.class);
diff --git a/v7/appcompat/tests/res/drawable/black_rect.xml b/v7/appcompat/tests/res/drawable/black_rect.xml
new file mode 100644
index 0000000..d1cd0c2
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/black_rect.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@android:color/black" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
index 2af99ac..1538e3a 100644
--- a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
+++ b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
@@ -77,6 +77,13 @@
             android:background="@drawable/test_background_green" />
 
         <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/view_untinted_deferred"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/sample_text2"
+            android:background="@drawable/black_rect" />
+
+        <android.support.v7.widget.AppCompatTextView
             android:id="@+id/view_text_color_hex"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
index 574ed6b..6e4516e 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
@@ -221,7 +221,7 @@
                     + ": expected all drawable colors to be "
                     + formatColorToHex(color)
                     + " but at position (" + centerX + "," + centerY + ") out of ("
-                    + bitmap.getWidth() + "," + bitmap.getHeight() + ") found"
+                    + bitmap.getWidth() + "," + bitmap.getHeight() + ") found "
                     + formatColorToHex(colorAtCenterPixel);
             if (throwExceptionIfFails) {
                 throw new RuntimeException(mismatchDescription);
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
index 933980d..bc2ad5c 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
@@ -16,29 +16,39 @@
 package android.support.v7.widget;
 
 import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.v7.testutils.TestUtilsActions.setEnabled;
 import static android.support.v7.testutils.TestUtilsActions.setTextAppearance;
+import static android.support.v7.testutils.TestUtilsMatchers.isBackground;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
 import android.graphics.Typeface;
 import android.os.Build;
+import android.support.annotation.ColorInt;
 import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
 import android.support.test.filters.SmallTest;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.view.ViewCompat;
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.appcompat.test.R;
+import android.view.View;
 import android.widget.TextView;
 
 import org.junit.Test;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * In addition to all tinting-related tests done by the base class, this class provides
  * tests specific to {@link AppCompatTextView} class.
@@ -51,6 +61,43 @@
         super(AppCompatTextViewActivity.class);
     }
 
+    /**
+     * This method tests that background tinting is applied when the call to
+     * {@link android.support.v4.view.ViewCompat#setBackgroundTintList(View, ColorStateList)}
+     * is done as a deferred event.
+     */
+    @Test
+    @MediumTest
+    public void testDeferredBackgroundTinting() throws Throwable {
+        onView(withId(R.id.view_untinted_deferred))
+                .check(matches(isBackground(0xff000000, true)));
+
+        final @ColorInt int oceanDefault = ResourcesCompat.getColor(
+                mResources, R.color.ocean_default, null);
+
+        final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
+                mResources, R.color.color_state_list_ocean, null);
+
+        // Emulate delay in kicking off the call to ViewCompat.setBackgroundTintList
+        Thread.sleep(200);
+        final CountDownLatch latch = new CountDownLatch(1);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TextView view = mActivity.findViewById(R.id.view_untinted_deferred);
+                ViewCompat.setBackgroundTintList(view, oceanColor);
+                latch.countDown();
+            }
+        });
+
+        assertTrue(latch.await(2, TimeUnit.SECONDS));
+
+        // Check that the background has switched to the matching entry in the newly set
+        // color state list.
+        onView(withId(R.id.view_untinted_deferred))
+                .check(matches(isBackground(oceanDefault, true)));
+    }
+
     @Test
     public void testAllCaps() {
         final String text1 = mResources.getString(R.string.sample_text1);
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index aa79213..17cd472 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -106,6 +106,7 @@
     method public abstract boolean areContentsTheSame(T2, T2);
     method public abstract boolean areItemsTheSame(T2, T2);
     method public abstract int compare(T2, T2);
+    method public java.lang.Object getChangePayload(T2, T2);
     method public abstract void onChanged(int, int);
     method public void onChanged(int, int, java.lang.Object);
   }
diff --git a/v7/recyclerview/src/main/java/android/support/v7/util/SortedList.java b/v7/recyclerview/src/main/java/android/support/v7/util/SortedList.java
index c62d0ce..af000a1 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/util/SortedList.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/util/SortedList.java
@@ -16,6 +16,8 @@
 
 package android.support.v7.util;
 
+import android.support.annotation.Nullable;
+
 import java.lang.reflect.Array;
 import java.util.Arrays;
 import java.util.Collection;
@@ -315,7 +317,8 @@
                 newDataStart++;
                 mOldDataStart++;
                 if (!mCallback.areContentsTheSame(oldItem, newItem)) {
-                    mCallback.onChanged(mMergedSize - 1, 1);
+                    mCallback.onChanged(mMergedSize - 1, 1,
+                            mCallback.getChangePayload(oldItem, newItem));
                 }
             } else {
                 // Old item is lower than or equal to (but not the same as the new). Output it.
@@ -401,7 +404,7 @@
                     return index;
                 } else {
                     mData[index] = item;
-                    mCallback.onChanged(index, 1);
+                    mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
                     return index;
                 }
             }
@@ -488,13 +491,13 @@
             if (cmp == 0) {
                 mData[index] = item;
                 if (contentsChanged) {
-                    mCallback.onChanged(index, 1);
+                    mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
                 }
                 return;
             }
         }
         if (contentsChanged) {
-            mCallback.onChanged(index, 1);
+            mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
         }
         // TODO this done in 1 pass to avoid shifting twice.
         removeItemAtIndex(index, false);
@@ -741,6 +744,28 @@
          * @return True if the two items represent the same object or false if they are different.
          */
         abstract public boolean areItemsTheSame(T2 item1, T2 item2);
+
+        /**
+         * When {@link #areItemsTheSame(T2, T2)} returns {@code true} for two items and
+         * {@link #areContentsTheSame(T2, T2)} returns false for them, {@link Callback} calls this
+         * method to get a payload about the change.
+         * <p>
+         * For example, if you are using {@link Callback} with
+         * {@link android.support.v7.widget.RecyclerView}, you can return the particular field that
+         * changed in the item and your
+         * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
+         * information to run the correct animation.
+         * <p>
+         * Default implementation returns {@code null}.
+         *
+         * @param item1 The first item to check.
+         * @param item2 The second item to check.
+         * @return A payload object that represents the changes between the two items.
+         */
+        @Nullable
+        public Object getChangePayload(T2 item1, T2 item2) {
+            return null;
+        }
     }
 
     /**
@@ -801,6 +826,11 @@
         }
 
         @Override
+        public void onChanged(int position, int count, Object payload) {
+            mBatchingListUpdateCallback.onChanged(position, count, payload);
+        }
+
+        @Override
         public boolean areContentsTheSame(T2 oldItem, T2 newItem) {
             return mWrappedCallback.areContentsTheSame(oldItem, newItem);
         }
@@ -810,6 +840,12 @@
             return mWrappedCallback.areItemsTheSame(item1, item2);
         }
 
+        @Nullable
+        @Override
+        public Object getChangePayload(T2 item1, T2 item2) {
+            return mWrappedCallback.getChangePayload(item1, item2);
+        }
+
         /**
          * This method dispatches any pending event notifications to the wrapped Callback.
          * You <b>must</b> always call this method after you are done with editing the SortedList.
diff --git a/v7/recyclerview/src/main/java/android/support/v7/widget/util/SortedListAdapterCallback.java b/v7/recyclerview/src/main/java/android/support/v7/widget/util/SortedListAdapterCallback.java
index 4921541..a1203a6 100644
--- a/v7/recyclerview/src/main/java/android/support/v7/widget/util/SortedListAdapterCallback.java
+++ b/v7/recyclerview/src/main/java/android/support/v7/widget/util/SortedListAdapterCallback.java
@@ -56,4 +56,9 @@
     public void onChanged(int position, int count) {
         mAdapter.notifyItemRangeChanged(position, count);
     }
+
+    @Override
+    public void onChanged(int position, int count, Object payload) {
+        mAdapter.notifyItemRangeChanged(position, count, payload);
+    }
 }
diff --git a/v7/recyclerview/src/test/java/android/support/v7/util/SortedListBatchedCallbackTest.java b/v7/recyclerview/src/test/java/android/support/v7/util/SortedListBatchedCallbackTest.java
index 3ace217..bc50415 100644
--- a/v7/recyclerview/src/test/java/android/support/v7/util/SortedListBatchedCallbackTest.java
+++ b/v7/recyclerview/src/test/java/android/support/v7/util/SortedListBatchedCallbackTest.java
@@ -50,6 +50,16 @@
     }
 
     @Test
+    public void onChangeWithPayload() {
+        final Object payload = 7;
+        mBatchedCallback.onChanged(1, 2, payload);
+        verifyZeroInteractions(mMockCallback);
+        mBatchedCallback.dispatchLastEvent();
+        verify(mMockCallback).onChanged(1, 2, payload);
+        verifyNoMoreInteractions(mMockCallback);
+    }
+
+    @Test
     public void onRemoved() {
         mBatchedCallback.onRemoved(2, 3);
         verifyZeroInteractions(mMockCallback);
diff --git a/v7/recyclerview/src/test/java/android/support/v7/util/SortedListTest.java b/v7/recyclerview/src/test/java/android/support/v7/util/SortedListTest.java
index da3c957..47d2ac0 100644
--- a/v7/recyclerview/src/test/java/android/support/v7/util/SortedListTest.java
+++ b/v7/recyclerview/src/test/java/android/support/v7/util/SortedListTest.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.util;
 
+import android.support.annotation.Nullable;
 import android.support.test.filters.SmallTest;
 
 import junit.framework.TestCase;
@@ -41,6 +42,8 @@
     List<Pair> mRemovals = new ArrayList<Pair>();
     List<Pair> mMoves = new ArrayList<Pair>();
     List<Pair> mUpdates = new ArrayList<Pair>();
+    private boolean mPayloadChanges = false;
+    List<PayloadChange> mPayloadUpdates = new ArrayList<>();
     private SortedList.Callback<Item> mCallback;
     InsertedCallback<Item> mInsertedCallback;
     ChangedCallback<Item> mChangedCallback;
@@ -97,6 +100,15 @@
             }
 
             @Override
+            public void onChanged(int position, int count, Object payload) {
+                if (mPayloadChanges) {
+                    mPayloadUpdates.add(new PayloadChange(position, count, payload));
+                } else {
+                    onChanged(position, count);
+                }
+            }
+
+            @Override
             public boolean areContentsTheSame(Item oldItem, Item newItem) {
                 return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data;
             }
@@ -105,6 +117,15 @@
             public boolean areItemsTheSame(Item item1, Item item2) {
                 return item1.id == item2.id;
             }
+
+            @Nullable
+            @Override
+            public Object getChangePayload(Item item1, Item item2) {
+                if (mPayloadChanges) {
+                    return item2.data;
+                }
+                return null;
+            }
         };
         mInsertedCallback = null;
         mChangedCallback = null;
@@ -705,6 +726,76 @@
         assertTrue(mAdditions.contains(new Pair(0, 6)));
     }
 
+    @Test
+    public void testAddExistingItemCallsChangeWithPayload() {
+        mList.addAll(
+                new Item(1, 10),
+                new Item(2, 20),
+                new Item(3, 30)
+        );
+        mPayloadChanges = true;
+
+        // add an item with the same id but a new data field i.e. send an update
+        final Item twoUpdate = new Item(2, 20);
+        twoUpdate.data = 1337;
+        mList.add(twoUpdate);
+        assertEquals(1, mPayloadUpdates.size());
+        final PayloadChange update = mPayloadUpdates.get(0);
+        assertEquals(1, update.position);
+        assertEquals(1, update.count);
+        assertEquals(1337, update.payload);
+        assertEquals(3, size());
+    }
+
+    @Test
+    public void testUpdateItemCallsChangeWithPayload() {
+        mList.addAll(
+                new Item(1, 10),
+                new Item(2, 20),
+                new Item(3, 30)
+        );
+        mPayloadChanges = true;
+
+        // add an item with the same id but a new data field i.e. send an update
+        final Item twoUpdate = new Item(2, 20);
+        twoUpdate.data = 1337;
+        mList.updateItemAt(1, twoUpdate);
+        assertEquals(1, mPayloadUpdates.size());
+        final PayloadChange update = mPayloadUpdates.get(0);
+        assertEquals(1, update.position);
+        assertEquals(1, update.count);
+        assertEquals(1337, update.payload);
+        assertEquals(3, size());
+        assertEquals(1337, mList.get(1).data);
+    }
+
+    @Test
+    public void testAddMultipleExistingItemCallsChangeWithPayload() {
+        mList.addAll(
+                new Item(1, 10),
+                new Item(2, 20),
+                new Item(3, 30)
+        );
+        mPayloadChanges = true;
+
+        // add two items with the same ids but a new data fields i.e. send two updates
+        final Item twoUpdate = new Item(2, 20);
+        twoUpdate.data = 222;
+        final Item threeUpdate = new Item(3, 30);
+        threeUpdate.data = 333;
+        mList.addAll(twoUpdate, threeUpdate);
+        assertEquals(2, mPayloadUpdates.size());
+        final PayloadChange update1 = mPayloadUpdates.get(0);
+        assertEquals(1, update1.position);
+        assertEquals(1, update1.count);
+        assertEquals(222, update1.payload);
+        final PayloadChange update2 = mPayloadUpdates.get(1);
+        assertEquals(2, update2.position);
+        assertEquals(1, update2.count);
+        assertEquals(333, update2.payload);
+        assertEquals(3, size());
+    }
+
     private int size() {
         return mList.size();
     }
@@ -821,4 +912,37 @@
             return result;
         }
     }
+
+    private static final class PayloadChange {
+        public final int position;
+        public final int count;
+        public final Object payload;
+
+        PayloadChange(int position, int count, Object payload) {
+            this.position = position;
+            this.count = count;
+            this.payload = payload;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            PayloadChange payloadChange = (PayloadChange) o;
+
+            if (position != payloadChange.position) return false;
+            if (count != payloadChange.count) return false;
+            return payload != null ? payload.equals(payloadChange.payload)
+                    : payloadChange.payload == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = position;
+            result = 31 * result + count;
+            result = 31 * result + (payload != null ? payload.hashCode() : 0);
+            return result;
+        }
+    }
 }
\ No newline at end of file
diff --git a/wear/res/drawable-v21/ws_ic_expand_more_white_22.xml b/wear/res/drawable-v23/ws_ic_expand_more_white_22.xml
similarity index 100%
rename from wear/res/drawable-v21/ws_ic_expand_more_white_22.xml
rename to wear/res/drawable-v23/ws_ic_expand_more_white_22.xml
diff --git a/wear/res/drawable-v21/ws_switch_thumb_material_anim.xml b/wear/res/drawable-v23/ws_switch_thumb_material_anim.xml
similarity index 100%
rename from wear/res/drawable-v21/ws_switch_thumb_material_anim.xml
rename to wear/res/drawable-v23/ws_switch_thumb_material_anim.xml
diff --git a/wear/res/layout-v23/ws_action_drawer_item_view.xml b/wear/res/layout-v23/ws_action_drawer_item_view.xml
index fc84862..a2e4640 100644
--- a/wear/res/layout-v23/ws_action_drawer_item_view.xml
+++ b/wear/res/layout-v23/ws_action_drawer_item_view.xml
@@ -15,7 +15,6 @@
 -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="@drawable/ws_action_item_background"
@@ -30,7 +29,7 @@
         android:tint="?android:attr/colorBackground"
         android:padding="@dimen/ws_action_drawer_item_icon_padding"
         android:scaleType="fitCenter"
-        tools:ignore="ContentDescription" />
+        android:importantForAccessibility="no"/>
 
     <TextView
         android:id="@+id/ws_action_drawer_item_text"
diff --git a/wear/res/values-v20/styles.xml b/wear/res/values-v20/styles.xml
deleted file mode 100644
index 92613f2..0000000
--- a/wear/res/values-v20/styles.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-
-    <style name="WsPageIndicatorViewStyle">
-        <item name="wsPageIndicatorDotSpacing">7.8dp</item>
-        <item name="wsPageIndicatorDotRadius">2.1dp</item>
-        <item name="wsPageIndicatorDotRadiusSelected">3.1dp</item>
-        <item name="wsPageIndicatorDotColor">?android:attr/colorForeground</item>
-        <item name="wsPageIndicatorDotColorSelected">?android:attr/colorForeground</item>
-        <item name="wsPageIndicatorDotFadeOutDelay">1000</item>
-        <item name="wsPageIndicatorDotFadeOutDuration">250</item>
-        <item name="wsPageIndicatorDotFadeInDuration">100</item>
-        <item name="wsPageIndicatorDotFadeWhenIdle">true</item>
-        <item name="wsPageIndicatorDotShadowColor">#66000000</item>
-        <item name="wsPageIndicatorDotShadowRadius">1dp</item>
-        <item name="wsPageIndicatorDotShadowDx">0.5dp</item>
-        <item name="wsPageIndicatorDotShadowDy">0.5dp</item>
-    </style>
-
-</resources>
diff --git a/wear/res/values-v23/styles.xml b/wear/res/values-v23/styles.xml
index 6bb1a51..63ed2d8 100644
--- a/wear/res/values-v23/styles.xml
+++ b/wear/res/values-v23/styles.xml
@@ -14,6 +14,22 @@
      limitations under the License.
 -->
 <resources>
+    <style name="WsPageIndicatorViewStyle">
+        <item name="wsPageIndicatorDotSpacing">7.8dp</item>
+        <item name="wsPageIndicatorDotRadius">2.1dp</item>
+        <item name="wsPageIndicatorDotRadiusSelected">3.1dp</item>
+        <item name="wsPageIndicatorDotColor">?android:attr/colorForeground</item>
+        <item name="wsPageIndicatorDotColorSelected">?android:attr/colorForeground</item>
+        <item name="wsPageIndicatorDotFadeOutDelay">1000</item>
+        <item name="wsPageIndicatorDotFadeOutDuration">250</item>
+        <item name="wsPageIndicatorDotFadeInDuration">100</item>
+        <item name="wsPageIndicatorDotFadeWhenIdle">true</item>
+        <item name="wsPageIndicatorDotShadowColor">#66000000</item>
+        <item name="wsPageIndicatorDotShadowRadius">1dp</item>
+        <item name="wsPageIndicatorDotShadowDx">0.5dp</item>
+        <item name="wsPageIndicatorDotShadowDy">0.5dp</item>
+    </style>
+
     <style name="WsWearableActionDrawerItemText">
         <item name="android:layout_gravity">center_vertical</item>
         <item name="android:ellipsize">end</item>
diff --git a/wear/res/values/styles.xml b/wear/res/values/styles.xml
index 44ab0b0..5af7e6f 100644
--- a/wear/res/values/styles.xml
+++ b/wear/res/values/styles.xml
@@ -29,6 +29,7 @@
         <item name="android:textSize">@dimen/ws_nav_drawer_text_size</item>
         <item name="android:gravity">center</item>
         <item name="android:maxLines">2</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="layout_marginTopPercent">@fraction/ws_nav_drawer_text_margin</item>
         <item name="layout_marginStartPercent">@fraction/ws_nav_drawer_text_margin</item>
         <item name="layout_marginEndPercent">@fraction/ws_nav_drawer_text_margin</item>
diff --git a/wear/src/main/java/android/support/wear/ambient/AmbientDelegate.java b/wear/src/main/java/android/support/wear/ambient/AmbientDelegate.java
index 4901290..8e96a02 100644
--- a/wear/src/main/java/android/support/wear/ambient/AmbientDelegate.java
+++ b/wear/src/main/java/android/support/wear/ambient/AmbientDelegate.java
@@ -19,14 +19,12 @@
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.util.Log;
 
 import com.google.android.wearable.compat.WearableActivityController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
-import java.lang.reflect.Method;
 
 /**
  * Provides compatibility for ambient mode.
@@ -146,19 +144,6 @@
     }
 
     /**
-     * Sets whether this activity's task should be moved to the front when the system exits ambient
-     * mode. If true, the activity's task may be moved to the front if it was the last activity to
-     * be running when ambient started, depending on how much time the system spent in ambient mode.
-     */
-    void setAutoResumeEnabled(boolean enabled) {
-        if (mWearableController != null) {
-            if (hasSetAutoResumeEnabledMethod()) {
-                mWearableController.setAutoResumeEnabled(enabled);
-            }
-        }
-    }
-
-    /**
      * @return {@code true} if the activity is currently in ambient.
      */
     boolean isAmbient() {
@@ -177,31 +162,4 @@
             mWearableController.dump(prefix, fd, writer, args);
         }
     }
-
-    private boolean hasSetAutoResumeEnabledMethod() {
-        if (!sInitAutoResumeEnabledMethod) {
-            sInitAutoResumeEnabledMethod = true;
-            try {
-                Method method =
-                        WearableActivityController.class
-                                .getDeclaredMethod("setAutoResumeEnabled", boolean.class);
-                // Proguard is sneaky -- it will actually rewrite strings it finds in addition to
-                // function names. Therefore add a "." prefix to the method name check to ensure the
-                // function was not renamed by proguard.
-                if (!(".setAutoResumeEnabled".equals("." + method.getName()))) {
-                    throw new NoSuchMethodException();
-                }
-                sHasAutoResumeEnabledMethod = true;
-            } catch (NoSuchMethodException e) {
-                Log.w(
-                        "WearableActivity",
-                        "Could not find a required method for auto-resume "
-                                + "support, likely due to proguard optimization. Please add "
-                                + "com.google.android.wearable:wearable jar to the list of library "
-                                + "jars for your project");
-                sHasAutoResumeEnabledMethod = false;
-            }
-        }
-        return sHasAutoResumeEnabledMethod;
-    }
 }
diff --git a/wear/src/main/java/android/support/wear/ambient/AmbientMode.java b/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
index b24f095..5db9383 100644
--- a/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
+++ b/wear/src/main/java/android/support/wear/ambient/AmbientMode.java
@@ -44,7 +44,7 @@
  * <p>
  * <pre class="prettyprint">{@code
  *     AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
- *     controller.setAutoResumeEnabled(true);
+ *     boolean isAmbient =  controller.isAmbient();
  * }</pre>
  */
 public final class AmbientMode extends Fragment {
@@ -117,7 +117,7 @@
          * Called when the system is updating the display for ambient mode. Activities may use this
          * opportunity to update or invalidate views.
          */
-        public void onUpdateAmbient() {};
+        public void onUpdateAmbient() {}
 
         /**
          * Called when an activity should exit ambient mode. This event is sent while an activity is
@@ -126,7 +126,7 @@
          * <p><em>Derived classes must call through to the super class's implementation of this
          * method. If they do not, an exception will be thrown.</em>
          */
-        public void onExitAmbient() {};
+        public void onExitAmbient() {}
     }
 
     private final AmbientDelegate.AmbientCallback mCallback =
diff --git a/wear/src/main/java/android/support/wear/ambient/SharedLibraryVersion.java b/wear/src/main/java/android/support/wear/ambient/SharedLibraryVersion.java
index cd90a3b..9421d9e 100644
--- a/wear/src/main/java/android/support/wear/ambient/SharedLibraryVersion.java
+++ b/wear/src/main/java/android/support/wear/ambient/SharedLibraryVersion.java
@@ -16,7 +16,6 @@
 package android.support.wear.ambient;
 
 import android.os.Build;
-import android.support.annotation.RestrictTo;
 import android.support.annotation.VisibleForTesting;
 
 import com.google.android.wearable.WearableSharedLib;
@@ -24,10 +23,7 @@
 /**
  * Internal class which can be used to determine the version of the wearable shared library that is
  * available on the current device.
- *
- * @hide
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 final class SharedLibraryVersion {
 
     private SharedLibraryVersion() {
diff --git a/wear/src/main/java/android/support/wear/ambient/WearableControllerProvider.java b/wear/src/main/java/android/support/wear/ambient/WearableControllerProvider.java
index 1682dc0..4b6ae8e 100644
--- a/wear/src/main/java/android/support/wear/ambient/WearableControllerProvider.java
+++ b/wear/src/main/java/android/support/wear/ambient/WearableControllerProvider.java
@@ -28,7 +28,7 @@
  *
  * @hide
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public class WearableControllerProvider {
 
     private static final String TAG = "WearableControllerProvider";
diff --git a/wear/src/main/java/android/support/wear/internal/widget/ResourcesUtil.java b/wear/src/main/java/android/support/wear/internal/widget/ResourcesUtil.java
index f23a688..8ba3adf 100644
--- a/wear/src/main/java/android/support/wear/internal/widget/ResourcesUtil.java
+++ b/wear/src/main/java/android/support/wear/internal/widget/ResourcesUtil.java
@@ -26,7 +26,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public final class ResourcesUtil {
 
     /**
diff --git a/wear/src/main/java/android/support/wear/internal/widget/drawer/MultiPagePresenter.java b/wear/src/main/java/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
index ad56048..4a7ce66 100644
--- a/wear/src/main/java/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
+++ b/wear/src/main/java/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
@@ -28,7 +28,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class MultiPagePresenter extends WearableNavigationDrawerPresenter {
 
     private final Ui mUi;
diff --git a/wear/src/main/java/android/support/wear/internal/widget/drawer/MultiPageUi.java b/wear/src/main/java/android/support/wear/internal/widget/drawer/MultiPageUi.java
index 9056845..0ba2f5d 100644
--- a/wear/src/main/java/android/support/wear/internal/widget/drawer/MultiPageUi.java
+++ b/wear/src/main/java/android/support/wear/internal/widget/drawer/MultiPageUi.java
@@ -16,6 +16,7 @@
 
 package android.support.wear.internal.widget.drawer;
 
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.RestrictTo.Scope;
@@ -37,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class MultiPageUi implements MultiPagePresenter.Ui {
 
     private static final String TAG = "MultiPageUi";
@@ -62,13 +63,8 @@
         final View content = inflater.inflate(R.layout.ws_navigation_drawer_view, drawer,
                 false /* attachToRoot */);
 
-        mNavigationPager =
-                (ViewPager) content
-                        .findViewById(R.id.ws_navigation_drawer_view_pager);
-        mPageIndicatorView =
-                (PageIndicatorView)
-                        content.findViewById(
-                                R.id.ws_navigation_drawer_page_indicator);
+        mNavigationPager = content.findViewById(R.id.ws_navigation_drawer_view_pager);
+        mPageIndicatorView = content.findViewById(R.id.ws_navigation_drawer_page_indicator);
 
         drawer.setDrawerContent(content);
     }
@@ -132,8 +128,9 @@
             mAdapter = adapter;
         }
 
+        @NonNull
         @Override
-        public Object instantiateItem(ViewGroup container, int position) {
+        public Object instantiateItem(@NonNull ViewGroup container, int position) {
             // Do not attach to root in the inflate method. The view needs to returned at the end
             // of this method. Attaching to root will cause view to point to container instead.
             final View view =
@@ -141,17 +138,17 @@
                             .inflate(R.layout.ws_navigation_drawer_item_view, container, false);
             container.addView(view);
             final ImageView iconView =
-                    (ImageView) view
-                            .findViewById(R.id.ws_navigation_drawer_item_icon);
+                    view.findViewById(R.id.ws_navigation_drawer_item_icon);
             final TextView textView =
-                    (TextView) view.findViewById(R.id.ws_navigation_drawer_item_text);
+                    view.findViewById(R.id.ws_navigation_drawer_item_text);
             iconView.setImageDrawable(mAdapter.getItemDrawable(position));
             textView.setText(mAdapter.getItemText(position));
             return view;
         }
 
         @Override
-        public void destroyItem(ViewGroup container, int position, Object object) {
+        public void destroyItem(@NonNull ViewGroup container, int position,
+                @NonNull Object object) {
             container.removeView((View) object);
         }
 
@@ -161,12 +158,12 @@
         }
 
         @Override
-        public int getItemPosition(Object object) {
+        public int getItemPosition(@NonNull Object object) {
             return POSITION_NONE;
         }
 
         @Override
-        public boolean isViewFromObject(View view, Object object) {
+        public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
             return view == object;
         }
     }
diff --git a/wear/src/main/java/android/support/wear/internal/widget/drawer/SinglePagePresenter.java b/wear/src/main/java/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
index d90b589..42cc7d0 100644
--- a/wear/src/main/java/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
+++ b/wear/src/main/java/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
@@ -29,7 +29,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class SinglePagePresenter extends WearableNavigationDrawerPresenter {
 
     private static final long DRAWER_CLOSE_DELAY_MS = 500;
diff --git a/wear/src/main/java/android/support/wear/internal/widget/drawer/SinglePageUi.java b/wear/src/main/java/android/support/wear/internal/widget/drawer/SinglePageUi.java
index f3a4290..ffc966f 100644
--- a/wear/src/main/java/android/support/wear/internal/widget/drawer/SinglePageUi.java
+++ b/wear/src/main/java/android/support/wear/internal/widget/drawer/SinglePageUi.java
@@ -38,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class SinglePageUi implements SinglePagePresenter.Ui {
 
     @IdRes
@@ -111,11 +111,10 @@
                         R.layout.ws_single_page_nav_drawer_peek_view, mDrawer,
                         false /* attachToRoot */);
 
-        mTextView = (TextView) content.findViewById(R.id.ws_nav_drawer_text);
+        mTextView = content.findViewById(R.id.ws_nav_drawer_text);
         mSinglePageImageViews = new CircledImageView[count];
         for (int i = 0; i < count; i++) {
-            mSinglePageImageViews[i] = (CircledImageView) content
-                    .findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
+            mSinglePageImageViews[i] = content.findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
             mSinglePageImageViews[i].setOnClickListener(new OnSelectedClickHandler(i, mPresenter));
             mSinglePageImageViews[i].setCircleHidden(true);
         }
diff --git a/wear/src/main/java/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java b/wear/src/main/java/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
index 1c8c4fb..df108aa 100644
--- a/wear/src/main/java/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
+++ b/wear/src/main/java/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
@@ -30,7 +30,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public abstract class WearableNavigationDrawerPresenter {
 
     private final Set<OnItemSelectedListener> mOnItemSelectedListeners = new HashSet<>();
diff --git a/wear/src/main/java/android/support/wear/utils/MetadataConstants.java b/wear/src/main/java/android/support/wear/utils/MetadataConstants.java
index 5be9c52..c7335c2 100644
--- a/wear/src/main/java/android/support/wear/utils/MetadataConstants.java
+++ b/wear/src/main/java/android/support/wear/utils/MetadataConstants.java
@@ -15,16 +15,13 @@
  */
 package android.support.wear.utils;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.os.Build;
 
 /**
  * Constants for android wear apps which are related to manifest meta-data.
  */
-@TargetApi(Build.VERSION_CODES.N)
 public class MetadataConstants {
 
     //  Constants for standalone apps. //
diff --git a/wear/src/main/java/android/support/wear/widget/BezierSCurveInterpolator.java b/wear/src/main/java/android/support/wear/widget/BezierSCurveInterpolator.java
index 131bae8..9c56a83 100644
--- a/wear/src/main/java/android/support/wear/widget/BezierSCurveInterpolator.java
+++ b/wear/src/main/java/android/support/wear/widget/BezierSCurveInterpolator.java
@@ -17,8 +17,6 @@
 package android.support.wear.widget;
 
 import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.os.Build;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.RestrictTo.Scope;
 
@@ -27,8 +25,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
+@RestrictTo(Scope.LIBRARY)
 class BezierSCurveInterpolator implements TimeInterpolator {
 
     /**
diff --git a/wear/src/main/java/android/support/wear/widget/BoxInsetLayout.java b/wear/src/main/java/android/support/wear/widget/BoxInsetLayout.java
index ba35f2c..a8b1381 100644
--- a/wear/src/main/java/android/support/wear/widget/BoxInsetLayout.java
+++ b/wear/src/main/java/android/support/wear/widget/BoxInsetLayout.java
@@ -20,7 +20,6 @@
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -111,21 +110,6 @@
     }
 
     @Override
-    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        insets = super.onApplyWindowInsets(insets);
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            final boolean round = insets.isRound();
-            if (round != mIsRound) {
-                mIsRound = round;
-                requestLayout();
-            }
-            mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
-                    insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
-        }
-        return insets;
-    }
-
-    @Override
     public void setForeground(Drawable drawable) {
         super.setForeground(drawable);
         mForegroundDrawable = drawable;
@@ -145,14 +129,10 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            requestApplyInsets();
-        } else {
-            mIsRound = getResources().getConfiguration().isScreenRound();
-            WindowInsets insets = getRootWindowInsets();
-            mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
-                    insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
-        }
+        mIsRound = getResources().getConfiguration().isScreenRound();
+        WindowInsets insets = getRootWindowInsets();
+        mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+                insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
     }
 
     @Override
@@ -413,7 +393,7 @@
     public static class LayoutParams extends FrameLayout.LayoutParams {
 
         /** @hide */
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
         @IntDef({BOX_NONE, BOX_LEFT, BOX_TOP, BOX_RIGHT, BOX_BOTTOM, BOX_ALL})
         @Retention(RetentionPolicy.SOURCE)
         public @interface BoxedEdges {}
diff --git a/wear/src/main/java/android/support/wear/widget/CircledImageView.java b/wear/src/main/java/android/support/wear/widget/CircledImageView.java
index 03ed8c9..c441dd5 100644
--- a/wear/src/main/java/android/support/wear/widget/CircledImageView.java
+++ b/wear/src/main/java/android/support/wear/widget/CircledImageView.java
@@ -19,7 +19,6 @@
 import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
@@ -32,7 +31,6 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.Px;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.RestrictTo.Scope;
@@ -47,8 +45,7 @@
  *
  * @hide
  */
-@TargetApi(Build.VERSION_CODES.M)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class CircledImageView extends View {
 
     private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();
@@ -133,13 +130,9 @@
         if (mDrawable != null && mDrawable.getConstantState() != null) {
             // The provided Drawable may be used elsewhere, so make a mutable clone before setTint()
             // or setAlpha() is called on it.
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                mDrawable =
-                        mDrawable.getConstantState()
-                                .newDrawable(context.getResources(), context.getTheme());
-            } else {
-                mDrawable = mDrawable.getConstantState().newDrawable(context.getResources());
-            }
+            mDrawable =
+                    mDrawable.getConstantState()
+                            .newDrawable(context.getResources(), context.getTheme());
             mDrawable = mDrawable.mutate();
         }
 
diff --git a/wear/src/main/java/android/support/wear/widget/CurvingLayoutCallback.java b/wear/src/main/java/android/support/wear/widget/CurvingLayoutCallback.java
index 275f1f8..5e88a8c 100644
--- a/wear/src/main/java/android/support/wear/widget/CurvingLayoutCallback.java
+++ b/wear/src/main/java/android/support/wear/widget/CurvingLayoutCallback.java
@@ -113,7 +113,7 @@
      */
     public void adjustAnchorOffsetXY(View child, float[] anchorOffsetXY) {
         return;
-    };
+    }
 
     @VisibleForTesting
     void setRound(boolean isScreenRound) {
diff --git a/wear/src/main/java/android/support/wear/widget/ProgressDrawable.java b/wear/src/main/java/android/support/wear/widget/ProgressDrawable.java
index 08e8ec2..28e0570 100644
--- a/wear/src/main/java/android/support/wear/widget/ProgressDrawable.java
+++ b/wear/src/main/java/android/support/wear/widget/ProgressDrawable.java
@@ -19,14 +19,12 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.RestrictTo.Scope;
 import android.util.Property;
@@ -37,8 +35,7 @@
  *
  * @hide
  */
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class ProgressDrawable extends Drawable {
 
     private static final Property<ProgressDrawable, Integer> LEVEL =
diff --git a/wear/src/main/java/android/support/wear/widget/RoundedDrawable.java b/wear/src/main/java/android/support/wear/widget/RoundedDrawable.java
index fd09a87..300b6dd 100644
--- a/wear/src/main/java/android/support/wear/widget/RoundedDrawable.java
+++ b/wear/src/main/java/android/support/wear/widget/RoundedDrawable.java
@@ -15,7 +15,6 @@
  */
 package android.support.wear.widget;
 
-import android.annotation.TargetApi;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
@@ -29,7 +28,6 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -76,7 +74,6 @@
  *   app:radius="dimension"
  *   app:clipEnabled="boolean" /&gt;</pre>
  */
-@TargetApi(Build.VERSION_CODES.N)
 public class RoundedDrawable extends Drawable {
 
     @VisibleForTesting
diff --git a/wear/src/main/java/android/support/wear/widget/ScrollManager.java b/wear/src/main/java/android/support/wear/widget/ScrollManager.java
index 8155f62..e01a271 100644
--- a/wear/src/main/java/android/support/wear/widget/ScrollManager.java
+++ b/wear/src/main/java/android/support/wear/widget/ScrollManager.java
@@ -16,11 +16,8 @@
 
 package android.support.wear.widget;
 
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.annotation.TargetApi;
-import android.os.Build;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
 import android.support.v7.widget.RecyclerView;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -30,8 +27,7 @@
  *
  * @hide
  */
-@TargetApi(Build.VERSION_CODES.M)
-@RestrictTo(LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class ScrollManager {
     // One second in milliseconds.
     private static final int ONE_SEC_IN_MS = 1000;
diff --git a/wear/src/main/java/android/support/wear/widget/SimpleAnimatorListener.java b/wear/src/main/java/android/support/wear/widget/SimpleAnimatorListener.java
index a60b0bd..3a1e56b 100644
--- a/wear/src/main/java/android/support/wear/widget/SimpleAnimatorListener.java
+++ b/wear/src/main/java/android/support/wear/widget/SimpleAnimatorListener.java
@@ -29,7 +29,7 @@
  * @hide Hidden until this goes through review
  */
 @RequiresApi(Build.VERSION_CODES.KITKAT_WATCH)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class SimpleAnimatorListener implements Animator.AnimatorListener {
 
     private boolean mWasCanceled;
diff --git a/wear/src/main/java/android/support/wear/widget/SwipeDismissLayout.java b/wear/src/main/java/android/support/wear/widget/SwipeDismissLayout.java
index 6e7a6f3..33da79c 100644
--- a/wear/src/main/java/android/support/wear/widget/SwipeDismissLayout.java
+++ b/wear/src/main/java/android/support/wear/widget/SwipeDismissLayout.java
@@ -16,12 +16,11 @@
 
 package android.support.wear.widget;
 
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
 import android.support.annotation.UiThread;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -40,7 +39,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 @UiThread
 class SwipeDismissLayout extends FrameLayout {
     private static final String TAG = "SwipeDismissLayout";
diff --git a/wear/src/main/java/android/support/wear/widget/WearableRecyclerView.java b/wear/src/main/java/android/support/wear/widget/WearableRecyclerView.java
index 5cacdfc..1425e68 100644
--- a/wear/src/main/java/android/support/wear/widget/WearableRecyclerView.java
+++ b/wear/src/main/java/android/support/wear/widget/WearableRecyclerView.java
@@ -16,11 +16,9 @@
 
 package android.support.wear.widget;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Point;
-import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.support.wear.R;
@@ -35,7 +33,6 @@
  *
  * @see #setCircularScrollingGestureEnabled(boolean)
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableRecyclerView extends RecyclerView {
     private static final String TAG = "WearableRecyclerView";
 
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java b/wear/src/main/java/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
index f1cb640..e9b2a40 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
@@ -32,7 +32,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class AbsListViewFlingWatcher implements FlingWatcher, OnScrollListener {
 
     private final FlingListener mListener;
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/FlingWatcherFactory.java b/wear/src/main/java/android/support/wear/widget/drawer/FlingWatcherFactory.java
index 3fe84c6..2fdfa13 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/FlingWatcherFactory.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/FlingWatcherFactory.java
@@ -33,7 +33,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class FlingWatcherFactory {
 
     /**
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java b/wear/src/main/java/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
index ca95ab2..4c0e5c8 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
@@ -38,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class NestedScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener {
 
     static final int MAX_WAIT_TIME_MS = 100;
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/PageIndicatorView.java b/wear/src/main/java/android/support/wear/widget/drawer/PageIndicatorView.java
index 99c7c09..1285f72 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/PageIndicatorView.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/PageIndicatorView.java
@@ -54,7 +54,7 @@
  * @hide
  */
 @RequiresApi(Build.VERSION_CODES.M)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 public class PageIndicatorView extends View implements OnPageChangeListener {
 
     private static final String TAG = "Dots";
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java b/wear/src/main/java/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
index 7570fae..7916875 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
@@ -31,7 +31,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class RecyclerViewFlingWatcher extends OnScrollListener implements FlingWatcher {
 
     private final FlingListener mListener;
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java b/wear/src/main/java/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
index f0b973b..5154e7b 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
@@ -38,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
 class ScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener {
 
     static final int MAX_WAIT_TIME_MS = 100;
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/WearableActionDrawerMenu.java b/wear/src/main/java/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
index 158467d..092ac72 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
@@ -16,12 +16,10 @@
 
 package android.support.wear.widget.drawer;
 
-import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.Nullable;
 import android.view.ActionProvider;
 import android.view.ContextMenu;
@@ -34,7 +32,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@TargetApi(Build.VERSION_CODES.M)
 /* package */ class WearableActionDrawerMenu implements Menu {
 
     private final Context mContext;
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/WearableActionDrawerView.java b/wear/src/main/java/android/support/wear/widget/drawer/WearableActionDrawerView.java
index 8154e6b..99cd4ff 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/WearableActionDrawerView.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/WearableActionDrawerView.java
@@ -16,12 +16,10 @@
 
 package android.support.wear.widget.drawer;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -38,6 +36,7 @@
 import android.view.MenuItem.OnMenuItemClickListener;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -75,7 +74,6 @@
  * <p>For {@link MenuItem}, setting and getting the title and icon, {@link MenuItem#getItemId}, and
  * {@link MenuItem#setOnMenuItemClickListener} are implemented.
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableActionDrawerView extends WearableDrawerView {
 
     private static final String TAG = "WearableActionDrawer";
@@ -140,12 +138,8 @@
             View peekView = layoutInflater.inflate(R.layout.ws_action_drawer_peek_view,
                     getPeekContainer(), false /* attachToRoot */);
             setPeekContent(peekView);
-            mPeekActionIcon =
-                    (ImageView) peekView
-                            .findViewById(R.id.ws_action_drawer_peek_action_icon);
-            mPeekExpandIcon =
-                    (ImageView) peekView
-                            .findViewById(R.id.ws_action_drawer_expand_icon);
+            mPeekActionIcon = peekView.findViewById(R.id.ws_action_drawer_peek_action_icon);
+            mPeekExpandIcon = peekView.findViewById(R.id.ws_action_drawer_expand_icon);
         } else {
             mPeekActionIcon = null;
             mPeekExpandIcon = null;
@@ -193,6 +187,16 @@
     }
 
     @Override
+    public void onDrawerOpened() {
+        if (mActionListAdapter.getItemCount() > 0) {
+            RecyclerView.ViewHolder holder = mActionList.findViewHolderForAdapterPosition(0);
+            if (holder != null && holder.itemView != null) {
+                holder.itemView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+            }
+        }
+    }
+
+    @Override
     public boolean canScrollHorizontally(int direction) {
         // Prevent the window from being swiped closed while it is open by saying that it can scroll
         // horizontally.
@@ -412,7 +416,6 @@
                 CharSequence title = mActionMenu.getItem(titleAwarePosition).getTitle();
                 holder.textView.setText(title);
                 holder.textView.setContentDescription(title);
-                holder.iconView.setContentDescription(title);
                 holder.iconView.setImageDrawable(icon);
             } else if (viewHolder instanceof TitleViewHolder) {
                 TitleViewHolder holder = (TitleViewHolder) viewHolder;
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/WearableDrawerLayout.java b/wear/src/main/java/android/support/wear/widget/drawer/WearableDrawerLayout.java
index 6d27064..e100a46 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/WearableDrawerLayout.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/WearableDrawerLayout.java
@@ -19,11 +19,10 @@
 import static android.support.wear.widget.drawer.WearableDrawerView.STATE_IDLE;
 import static android.support.wear.widget.drawer.WearableDrawerView.STATE_SETTLING;
 
-import android.annotation.TargetApi;
 import android.content.Context;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.view.NestedScrollingParent;
@@ -98,7 +97,6 @@
  *     &lt;/android.support.wear.widget.drawer.WearableDrawerView&gt;
  * &lt;/android.support.wear.widget.drawer.WearableDrawerLayout&gt;</pre>
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableDrawerLayout extends FrameLayout
         implements View.OnLayoutChangeListener, NestedScrollingParent, FlingListener {
 
@@ -654,12 +652,13 @@
     }
 
     @Override // NestedScrollingParent
-    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+    public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY,
+            boolean consumed) {
         return false;
     }
 
     @Override // NestedScrollingParent
-    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+    public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
         maybeUpdateScrollingContentView(target);
         mLastScrollWasFling = true;
 
@@ -674,13 +673,13 @@
     }
 
     @Override // NestedScrollingParent
-    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
         maybeUpdateScrollingContentView(target);
     }
 
     @Override // NestedScrollingParent
-    public void onNestedScroll(
-            View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed) {
 
         boolean scrolledUp = dyConsumed < 0;
         boolean scrolledDown = dyConsumed > 0;
@@ -873,18 +872,20 @@
     }
 
     @Override // NestedScrollingParent
-    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target,
+            int nestedScrollAxes) {
         mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
     }
 
     @Override // NestedScrollingParent
-    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target,
+            int nestedScrollAxes) {
         mCurrentNestedScrollSlopTracker = 0;
         return true;
     }
 
     @Override // NestedScrollingParent
-    public void onStopNestedScroll(View target) {
+    public void onStopNestedScroll(@NonNull View target) {
         mNestedScrollingParentHelper.onStopNestedScroll(target);
     }
 
@@ -961,7 +962,7 @@
         public abstract WearableDrawerView getDrawerView();
 
         @Override
-        public boolean tryCaptureView(View child, int pointerId) {
+        public boolean tryCaptureView(@NonNull View child, int pointerId) {
             WearableDrawerView drawerView = getDrawerView();
             // Returns true if the dragger is dragging the drawer.
             return child == drawerView && !drawerView.isLocked()
@@ -969,13 +970,13 @@
         }
 
         @Override
-        public int getViewVerticalDragRange(View child) {
+        public int getViewVerticalDragRange(@NonNull View child) {
             // Defines the vertical drag range of the drawer.
             return child == getDrawerView() ? child.getHeight() : 0;
         }
 
         @Override
-        public void onViewCaptured(View capturedChild, int activePointerId) {
+        public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
             showDrawerContentMaybeAnimate((WearableDrawerView) capturedChild);
         }
 
@@ -1036,7 +1037,7 @@
     private class TopDrawerDraggerCallback extends DrawerDraggerCallback {
 
         @Override
-        public int clampViewPositionVertical(View child, int top, int dy) {
+        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
             if (mTopDrawerView == child) {
                 int peekHeight = mTopDrawerView.getPeekContainer().getHeight();
                 // The top drawer can be dragged vertically from peekHeight - height to 0.
@@ -1063,7 +1064,7 @@
         }
 
         @Override
-        public void onViewReleased(View releasedChild, float xvel, float yvel) {
+        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
             if (releasedChild == mTopDrawerView) {
                 // Settle to final position. Either swipe open or close.
                 final float openedPercent = mTopDrawerView.getOpenedPercent();
@@ -1085,7 +1086,8 @@
         }
 
         @Override
-        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+        public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,
+                int dy) {
             if (changedView == mTopDrawerView) {
                 // Compute the offset and invalidate will move the drawer during layout.
                 final int height = changedView.getHeight();
@@ -1106,7 +1108,7 @@
     private class BottomDrawerDraggerCallback extends DrawerDraggerCallback {
 
         @Override
-        public int clampViewPositionVertical(View child, int top, int dy) {
+        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
             if (mBottomDrawerView == child) {
                 // The bottom drawer can be dragged vertically from (parentHeight - height) to
                 // (parentHeight - peekHeight).
@@ -1131,7 +1133,7 @@
         }
 
         @Override
-        public void onViewReleased(View releasedChild, float xvel, float yvel) {
+        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
             if (releasedChild == mBottomDrawerView) {
                 // Settle to final position. Either swipe open or close.
                 final int parentHeight = getHeight();
@@ -1151,7 +1153,8 @@
         }
 
         @Override
-        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+        public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,
+                int dy) {
             if (changedView == mBottomDrawerView) {
                 // Compute the offset and invalidate will move the drawer during layout.
                 final int height = changedView.getHeight();
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/WearableDrawerView.java b/wear/src/main/java/android/support/wear/widget/drawer/WearableDrawerView.java
index dafac39..2462cba 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/WearableDrawerView.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/WearableDrawerView.java
@@ -16,11 +16,9 @@
 
 package android.support.wear.widget.drawer;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.support.annotation.IdRes;
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
@@ -87,7 +85,6 @@
  *     &lt;/LinearLayout&gt;
  * &lt;/android.support.wear.widget.drawer.WearableDrawerView&gt;</pre>
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableDrawerView extends FrameLayout {
     /**
      * Indicates that the drawer is in an idle, settled state. No animation is in progress.
@@ -109,7 +106,7 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(Scope.LIBRARY_GROUP)
+    @RestrictTo(Scope.LIBRARY)
     @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
     public @interface DrawerState {}
 
@@ -155,8 +152,8 @@
         setElevation(context.getResources()
                 .getDimension(R.dimen.ws_wearable_drawer_view_elevation));
 
-        mPeekContainer = (ViewGroup) findViewById(R.id.ws_drawer_view_peek_container);
-        mPeekIcon = (ImageView) findViewById(R.id.ws_drawer_view_peek_icon);
+        mPeekContainer = findViewById(R.id.ws_drawer_view_peek_container);
+        mPeekIcon = findViewById(R.id.ws_drawer_view_peek_icon);
 
         mPeekContainer.setOnClickListener(
                 new OnClickListener() {
diff --git a/wear/src/main/java/android/support/wear/widget/drawer/WearableNavigationDrawerView.java b/wear/src/main/java/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
index 480812b..c5c49fe 100644
--- a/wear/src/main/java/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
+++ b/wear/src/main/java/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
@@ -16,11 +16,9 @@
 
 package android.support.wear.widget.drawer;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.support.annotation.IntDef;
@@ -58,7 +56,6 @@
  * <p>The developer may specify which style to use with the {@code app:navigationStyle} custom
  * attribute. If not specified, {@link #SINGLE_PAGE singlePage} will be used as the default.
  */
-@TargetApi(Build.VERSION_CODES.M)
 public class WearableNavigationDrawerView extends WearableDrawerView {
 
     private static final String TAG = "WearableNavDrawer";
@@ -79,7 +76,7 @@
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
-    @RestrictTo(Scope.LIBRARY_GROUP)
+    @RestrictTo(Scope.LIBRARY)
     @IntDef({SINGLE_PAGE, MULTI_PAGE})
     public @interface NavigationStyle {}
 
@@ -282,7 +279,7 @@
         /**
          * @hide
          */
-        @RestrictTo(Scope.LIBRARY_GROUP)
+        @RestrictTo(Scope.LIBRARY)
         public void setPresenter(WearableNavigationDrawerPresenter presenter) {
             mPresenter = presenter;
         }